Luogu P5469 [NOI2019]机器人 (DP、多项式)

2024-02-15 15:48

本文主要是介绍Luogu P5469 [NOI2019]机器人 (DP、多项式),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

不用FFT的多项式(大雾)

题目链接: https://www.luogu.org/problemnew/show/P5469

(这题在洛谷都成绿题了海星)

题解: 首先我们考虑,一个序列位置最右边的最大值可以走遍整个序列,并且其余任何点都不能跨过这个位置。

所以我们可以区间dp, \(dp[l][r][x]\)表示区间\([l,r]\)最大值不超过\(x\)的方案数,枚举最大值点\(mid\)及其值\(k\), \(dp[l][r][x]=\sum_{mid}\sum_{k}dp[l][mid-1][k]\times dp[mid+1][r][k-1]\), 也可以设\(dp[l][r][x]\)表示区间\([l,r]\)的最大值恰好为\(x\)的方案数,枚举最大值点\(mid\)则有\(dp[l][r][x]=\sum_{mid}\sum_{k\le x}dp[l][mid-1][k]\sum_{k<x}dp[mid+1][r][k]\).

可获得\(35\)分,当然如果你有梦想数组开大点卡卡常就有\(50\)分了。(然而我在考场上没梦想\(35\)分滚粗了)

然后正解的话,恰好为\(x\)那种状态比较好。

首先离散化,那么我们发现当\(k\)在每一段区间内时,转移是类似的。

一个结论是,当\(k\)在某一段区间内时\(dp[l][r][k]\)是关于\(k\)的不超过\((r-l)\)次多项式。

证明: 首先\(l=r\)时显然是\(0\)次多项式,当\(l<r\)时,我们枚举\(mid\)然后左边有一个\(mid-1-l\)次多项式右边有一个\(r-mid-1\)次多项式,又因为转移要对左右两边多项式做前缀和再相乘(这个具体见下一段),所以次数要\(+1\)(\(k\)次多项式的前缀和是\((k+1)\)次多项式),所以总次数为\((mid-1-l+1)+(r-mid-1+1)=r-l\).

这里解释一下如何转移: \(dp[l][r][x]\), 左右两边分开考虑,考虑现在枚举的\(k\), 假设\(k\)\(x\)区间前面的区间里,那么这个值与\(x\)区间内的自变量无关了,变成了“常数”(因为这个区间所包含的数无论如何都比自变量小),而这个“常数”的值就是\(dp[l][r][k']\) (\(k'\)\(k\)所在的区间)这个多项式在每个\(k'\)区间内的点的点值之和,把这个值加到\(dp[l][r][x]\)多项式的常数项里。

假设\(k\)\(x\)区间里,那么新的多项式直接就等于这个多项式在区间内的小于等于自变量的前缀和(如果现在枚举的是左边),或者多项式在区间内小于自变量的前缀和(如果现在枚举的是右边)。

于是记忆化搜索一波,使用多项式前缀和进行转移,这样枚举\(mid\)之后复杂度为多项式次数的平方。

多项式前缀和需要预处理\(s_k(x)=\sum^{x}_{i=0}x^k\), 这是一个\((k+1)\)次多项式,所以Lagrange插值求出来系数。据说有其他的搞法,但是我只能想到这一种。

裸做\(80\)分起步(我裸做了一波得了\(85\))

剪枝优化可获得\(100\)分。

个人感觉这题部分分设置得真的特别合理,给出题人点赞!(我是认真的)

好难写啊,我好菜啊……

代码

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<vector>
#include<algorithm>
#define llong long long
using namespace std;const int P = 1e9+7;
const int N = 301;llong quickpow(llong x,llong y)
{llong cur = x,ret = 1ll;for(int i=0; y; i++){if(y&(1ll<<i)) {y-=(1ll<<i); ret = ret*cur%P;}cur = cur*cur%P;}return ret;
}
llong mulinv(llong x) {return quickpow(x,P-2);}llong aux[N+4],aux2[N+4];
struct Polynomial
{vector<llong> a; int n;Polynomial() {}Polynomial(int _n) {n = _n; for(int i=0; i<=n; i++) a.push_back(0ll);}void clear() {n = 0; a.clear(); a.push_back(0ll);}void output() {printf("deg%d, ",n); for(int i=0; i<=n; i++) printf("%lld ",a[i]); puts("");}Polynomial operator +(Polynomial &arg) const{Polynomial ret(max(n,arg.n));for(int i=0; i<=min(n,arg.n); i++){ret.a[i] = (a[i]+arg.a[i])%P;}for(int i=min(n,arg.n)+1; i<=n; i++) ret.a[i] = a[i];for(int i=min(n,arg.n)+1; i<=arg.n; i++) ret.a[i] = arg.a[i];return ret;}Polynomial operator -(Polynomial &arg) const{Polynomial ret(max(n,arg.n));for(int i=0; i<=min(n,arg.n); i++){ret.a[i] = (a[i]-arg.a[i]+P)%P;}for(int i=min(n,arg.n)+1; i<=n; i++) ret.a[i] = a[i];for(int i=min(n,arg.n)+1; i<=arg.n; i++) ret.a[i] = P-arg.a[i];return ret;}Polynomial operator *(Polynomial &arg) const{Polynomial ret(n+arg.n);for(int i=0; i<=n; i++){for(int j=0; j<=arg.n; j++){ret.a[i+j] = (ret.a[i+j]+a[i]*arg.a[j])%P;}}return ret;}llong calc(llong x){llong ret = 0ll;for(int i=n; i>=0; i--){ret = (ret*x+a[i])%P;}return ret;}void interpoly(int _n,llong ax[],llong ay[]){n = _n; for(int i=0; i<=n; i++) a.push_back(0ll);for(int i=0; i<=n+1; i++) aux[i] = 0ll;aux[0] = 1ll;for(int i=0; i<=n; i++){for(int j=i+1; j>0; j--){aux[j] = (aux[j-1]-aux[j]*ax[i]%P+P)%P;}aux[0] = P-aux[0]*ax[i]%P;}for(int i=0; i<=n; i++){llong tmp = 1ll;for(int j=0; j<=n; j++){if(i==j) continue;tmp = tmp*(ax[i]-ax[j]+P)%P;}llong coe = mulinv(tmp);for(int j=n+1; j>=0; j--) {aux2[j] = aux[j];}for(int j=n; j>=0; j--){a[j] = (a[j]+aux2[j+1]*coe%P*ay[i])%P;aux2[j] = (aux2[j]+ax[i]*aux2[j+1])%P;}}}
};
Polynomial tmp1,tmp2,tmp3;
Polynomial dp[2661][(N<<1)+3],sdp[2661][(N<<1)+3];
llong lval[2661][(N<<1)+3],rval[2661][(N<<1)+3];
int dpid[N+4][N+4];
Polynomial spw[N+4];
struct Interval
{llong lb,rb; //[1,2n]
} a[N+3];
vector<llong> disc;
llong spwx[N+3],spwy[N+3];
int mx[N+3][N+3];
int n,nsta;
llong ans;int getid(llong x) {return lower_bound(disc.begin(),disc.end(),x)-disc.begin();} //no +1Polynomial prefixsum(Polynomial poly)
{Polynomial ret(poly.n+1);for(int i=0; i<=poly.n; i++){for(int j=0; j<=i+1; j++){ret.a[j] = (ret.a[j]+poly.a[i]*spw[i].a[j])%P;}}return ret;
}void dfs(int l,int r,int x)
{Polynomial tmp1,tmp2,tmp3;if(dpid[l][r] && dp[dpid[l][r]][x].a.size()>0) {return;}if(!dpid[l][r]) {nsta++; dpid[l][r] = nsta;}if(l==r){if(!dpid[l][r]) {nsta++; dpid[l][r] = nsta;}dp[dpid[l][r]][x] = Polynomial(0); dp[dpid[l][r]][x].a[0] = (x>=a[l].lb&&x<=a[l].rb) ? 1ll : 0ll;sdp[dpid[l][r]][x] = prefixsum(dp[dpid[l][r]][x]);lval[dpid[l][r]][x] = sdp[dpid[l][r]][x].calc(disc[x-1]);rval[dpid[l][r]][x] = sdp[dpid[l][r]][x].calc(disc[x]);return;}dp[dpid[l][r]][x].clear(); sdp[dpid[l][r]][x].clear();if(mx[l][r]>x) return;for(int lenl=(r-l+1)>>1; lenl<=(r-l+1)+1-((r-l+1)>>1); lenl++){int mid = l+lenl-1;if(!(x>=a[mid].lb && x<=a[mid].rb)) {continue;} //注意此处要特判tmp1.clear(); tmp2.clear();if(mid>l){for(int k=1; k<=x; k++){dfs(l,mid-1,k);if(k<x){tmp1.a[0] = (tmp1.a[0]+rval[dpid[l][mid-1]][k]-lval[dpid[l][mid-1]][k]+P)%P;}else{tmp1 = tmp1+sdp[dpid[l][mid-1]][k];tmp1.a[0] = (tmp1.a[0]-lval[dpid[l][mid-1]][k]+P)%P;}}}else{tmp1 = Polynomial(0); tmp1.a[0] = 1ll;}if(mid<r){for(int k=0; k<=x; k++){dfs(mid+1,r,k);if(k<x){tmp2.a[0] = (tmp2.a[0]+rval[dpid[mid+1][r]][k]-lval[dpid[mid+1][r]][k]+P)%P;}else{tmp2 = tmp2+sdp[dpid[mid+1][r]][k];tmp2 = tmp2-dp[dpid[mid+1][r]][k];tmp2.a[0] = (tmp2.a[0]-lval[dpid[mid+1][r]][k]+P)%P;}}}else{tmp2 = Polynomial(0); tmp2.a[0] = 1ll;}tmp3 = tmp1*tmp2;dp[dpid[l][r]][x] = dp[dpid[l][r]][x]+tmp3;}sdp[dpid[l][r]][x] = prefixsum(dp[dpid[l][r]][x]);lval[dpid[l][r]][x] = sdp[dpid[l][r]][x].calc(disc[x-1]);rval[dpid[l][r]][x] = sdp[dpid[l][r]][x].calc(disc[x]);
}int main()
{scanf("%d",&n);for(int i=1; i<=n; i++) {scanf("%lld%lld",&a[i].lb,&a[i].rb); disc.push_back(a[i].lb-1); disc.push_back(a[i].rb);}for(int i=0; i<=n; i++){spwx[0] = 0ll; spwy[0] = 0ll;for(int j=1; j<=i+1; j++){spwx[j] = j;spwy[j] = (spwy[j-1]+quickpow(j,i))%P;}spw[i].interpoly(i+1,spwx,spwy);}sort(disc.begin(),disc.end()); disc.erase(unique(disc.begin(),disc.end()),disc.end());for(int i=1; i<=n; i++) {a[i].lb = getid(a[i].lb); a[i].rb = getid(a[i].rb);}nsta = 1; for(int i=0; i<disc.size(); i++){dp[1][i] = Polynomial(0); dp[1][i].a[0] = 1ll;sdp[1][i] = prefixsum(dp[1][i]);lval[1][i] = sdp[1][i].calc(disc[i-1]);rval[1][i] = sdp[1][i].calc(disc[i]);}for(int i=1; i<=n; i++){mx[i][i] = a[i].lb;for(int j=i+1; j<=n; j++){mx[i][j] = max(mx[i][j-1],(int)a[j].lb);}}ans = 0ll;for(int i=1; i<disc.size(); i++){dfs(1,n,i);ans = (ans+sdp[dpid[1][n]][i].calc(disc[i])-sdp[dpid[1][n]][i].calc(disc[i-1])+P)%P;}printf("%lld\n",ans);return 0;
}

这篇关于Luogu P5469 [NOI2019]机器人 (DP、多项式)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/711821

相关文章

hdu4826(三维DP)

这是一个百度之星的资格赛第四题 题目链接:http://acm.hdu.edu.cn/contests/contest_showproblem.php?pid=1004&cid=500 题意:从左上角的点到右上角的点,每个点只能走一遍,走的方向有三个:向上,向下,向右,求最大值。 咋一看像搜索题,先暴搜,TLE,然后剪枝,还是TLE.然后我就改方法,用DP来做,这题和普通dp相比,多个个向上

hdu1011(背包树形DP)

没有完全理解这题, m个人,攻打一个map,map的入口是1,在攻打某个结点之前要先攻打其他一个结点 dp[i][j]表示m个人攻打以第i个结点为根节点的子树得到的最优解 状态转移dp[i][ j ] = max(dp[i][j], dp[i][k]+dp[t][j-k]),其中t是i结点的子节点 代码如下: #include<iostream>#include<algorithm

hdu4865(概率DP)

题意:已知前一天和今天的天气概率,某天的天气概率和叶子的潮湿程度的概率,n天叶子的湿度,求n天最有可能的天气情况。 思路:概率DP,dp[i][j]表示第i天天气为j的概率,状态转移如下:dp[i][j] = max(dp[i][j, dp[i-1][k]*table2[k][j]*table1[j][col] )  代码如下: #include <stdio.h>#include

usaco 1.1 Broken Necklace(DP)

直接上代码 接触的第一道dp ps.大概的思路就是 先从左往右用一个数组在每个点记下蓝或黑的个数 再从右到左算一遍 最后取出最大的即可 核心语句在于: 如果 str[i] = 'r'  ,   rl[i]=rl[i-1]+1, bl[i]=0 如果 str[i] = 'b' ,  bl[i]=bl[i-1]+1, rl[i]=0 如果 str[i] = 'w',  bl[i]=b

uva 10154 DP 叠乌龟

题意: 给你几只乌龟,每只乌龟有自身的重量和力量。 每只乌龟的力量可以承受自身体重和在其上的几只乌龟的体重和内。 问最多能叠放几只乌龟。 解析: 先将乌龟按力量从小到大排列。 然后dp的时候从前往后叠,状态转移方程: dp[i][j] = dp[i - 1][j];if (dp[i - 1][j - 1] != inf && dp[i - 1][j - 1] <= t[i]

uva 10118 dP

题意: 给4列篮子,每次从某一列开始无放回拿蜡烛放入篮子里,并且篮子最多只能放5支蜡烛,数字代表蜡烛的颜色。 当拿出当前颜色的蜡烛在篮子里存在时,猪脚可以把蜡烛带回家。 问最多拿多少只蜡烛。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cs

uva 10069 DP + 大数加法

代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#include <stack>#include <vector>#include <queue>#include <map>#include <cl

uva 10029 HASH + DP

题意: 给一个字典,里面有好多单词。单词可以由增加、删除、变换,变成另一个单词,问能变换的最长单词长度。 解析: HASH+dp 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <cmath>#inc

XTU 1233 n个硬币连续m个正面个数(dp)

题面: Coins Problem Description: Duoxida buys a bottle of MaiDong from a vending machine and the machine give her n coins back. She places them in a line randomly showing head face or tail face o

dp算法练习题【8】

不同二叉搜索树 96. 不同的二叉搜索树 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。 示例 1: 输入:n = 3输出:5 示例 2: 输入:n = 1输出:1 class Solution {public int numTrees(int n) {int[] dp = new int