LOJ#3157. 「NOI2019」机器人 DP+拉格朗日插值
NOI2019 两道插值可还行.
一个数不可能向右移动到超过后缀最大值的位置,也不可能向前移到前缀最大值之前的位置.
那么就考虑基于最大值的分治(DP)
令 $f[l][r][x]$ 表示当前区间为 $[l,r]$ 最大值为 $x$ 的方案数.
然后转移的话枚举 $k$ 为最大值出现的位置(有多个的话则是最后出现的位置).
那么就将问题分成两个子问题了,时间复杂度为 $O(nMW)$,其中 $M,W$ 分别为区间个数和值域.
$35$ pts 的暴力分就是 $M=n^2$,即每个区间都枚举,总复杂度是 $O(n^2W)$.
但是我们打表发现有用的区间 $M$ 最多为 $3000$,那么提前记忆化搜索的话复杂度就是 $O(10nW)$ 了.
考虑 $l=1,r=10^9$ 的点,由于每个位置的取值范围都是相同的,我们可以暴力求出最大值为 $1$ ~ $n$ 的点.
然后可以用容斥+组合来算.
还有一种能推广到正解的做法就是用拉格朗日插值法.
可以归纳,$f[l][r]$ 是一个不超过 $r-l$ 次的多项式.
考虑当 $l=r$ 时显然成立(就是一个常数),然后对 $f$ 求前缀和的话多项式的次数+1.
最后在合并的时候本质上是两个多项式相乘,次数为 $len-1$.
对于每个位置取值不同的点就将所有点排序,然后以相邻两个点为值域仿照上面的做法去做 DP.
那么假设当前的区间为 $[a,b]$ 那么就将 $[1,a)$ 的部分当作常数项处理.
这里有两个细节要注意:
1. 加入区间的时候要加入 $[a,b)$ 因为如果加入 $[a,a]$ 的话可能会处理不到 $[a,a]$ 这种情况.
2. 处理到 $[a_{i-1},a_{i}]$ 的时候要先处理 $[a_{i-1},a_{i}-1]$ 然后再处理 $[a_{i},a_{i}]$,因为端点处可能存在交界.
code:
#include <cstdio> #include <ctime> #include <vector> #include <cmath> #include <cstring> #include <algorithm> #define N 307 #define M 3005 #define ll long long #define mod 1000000007 #define setIO(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout) using namespace std; int qpow(int x,int y) { int tmp=1; for(;y;y>>=1,x=(ll)x*x%mod) { if(y&1) tmp=(ll)tmp*x%mod; } return tmp; } inline int get_inv(int x) { return qpow(x,mod-2); } inline int ADD(int x,int y) { return x+y>=mod?x+y-mod:x+y; } inline int DEC(int x,int y) { return x-y<0?x-y+mod:x-y; } namespace Lagrange { int x[N],y[N],fac[N],inv[N],pre[N],suf[N]; void prep() { fac[0]=1; for(int i=1;i<N;++i) fac[i]=(ll)fac[i-1]*i%mod; inv[1]=1; for(int i=2;i<N;++i) { inv[i]=(ll)(mod-mod/i)*inv[mod%i]%mod; } inv[0]=1; for(int i=1;i<N;++i) { inv[i]=(ll)inv[i-1]*inv[i]%mod; } } void init(int v,int kth) { for(int i=0;i<=kth;++i) x[i]=i; pre[0]=suf[kth+1]=1; for(int i=1;i<=kth;++i) { pre[i]=(ll)(v-x[i-1]+mod)*pre[i-1]%mod; } for(int i=kth;i>=1;--i) { suf[i]=(ll)(v-x[i]+mod)*suf[i+1]%mod; } } int solve(int v,int kth) { int an=0; for(int i=0;i<=kth;++i) { int up=1,dn=1; dn=(ll)inv[i]*inv[kth-i]%mod; if((kth-i)&1) dn=(ll)dn*(mod-1)%mod; up=(ll)pre[i]*suf[i+1]%mod; an=ADD(an,(ll)y[i]*up%mod*dn%mod); } return an; } }; int n; bool vis[M]; int a[N],b[N],e[M],lim,pr; int id[N][N],dp[M][N],len[M],tot,cnt; struct data { int l,r; data(int l=0,int r=0):l(l),r(r){} }arr[M]; void dfs(int l,int r) { if(l>r||id[l][r]) return; id[l][r]=++cnt; arr[cnt]=data(l,r); len[cnt]=r-l+1; if(l==r) return; int mid=(l+r)>>1,len=r-l+1; if(len&1) { dfs(l,mid-2),dfs(l,mid-1),dfs(l,mid); dfs(mid+2,r),dfs(mid+1,r),dfs(mid,r); } else { dfs(l,mid-1),dfs(l,mid); dfs(mid+2,r),dfs(mid+1,r); } } void get(int l,int r,int x,int now); void solve(int l,int r) { int now=id[l][r]; if(vis[now]||l>r) return; vis[now]=1; for(int i=1;i<=lim;++i) { dp[now][i]=0; } if(l==r) { for(int i=1;i<=lim;++i) { dp[now][i]=dp[now][i-1]+(i+pr>=a[l]&&i+pr<=b[l]); if(dp[now][i]>=mod) dp[now][i]-=mod; } return; } int mid=(l+r)>>1,len=(r-l+1); if(len&1) { get(l,r,mid-1,now); get(l,r,mid,now); get(l,r,mid+1,now); } else { get(l,r,mid,now); get(l,r,mid+1,now); } for(int i=1;i<=lim;++i) { dp[now][i]=ADD(dp[now][i],dp[now][i-1]); } } void get(int l,int r,int x,int now) { if(x<l||x>r) return; solve(l,x-1),solve(x+1,r); int u=id[l][x-1],v=id[x+1][r]; for(int i=1;i<=lim;++i) { if(i+pr>=a[x]&&i+pr<=b[x]) dp[now][i]=ADD(dp[now][i],(ll)dp[u][i]*dp[v][i-1]%mod); } } int main() { // setIO("robot"); scanf("%d",&n); for(int i=1;i<=n;++i) { scanf("%d%d",&a[i],&b[i]); e[++tot]=a[i],e[++tot]=b[i]+1; } Lagrange::prep(); sort(e+1,e+1+tot); dfs(1,n); int x,y,z; for(int i=0;i<=n+3;++i) { dp[0][i]=1; } for(int i=1;i<=tot;++i) { if(e[i]!=e[i-1]) { pr=e[i-1]; lim=min(e[i]-e[i-1]-1,n+3); memset(vis,false,sizeof(vis)); solve(1,n); if(lim==e[i]-e[i-1]-1) { for(int j=1;j<=cnt;++j) dp[j][0]=dp[j][lim]; } else { for(int j=1;j<=cnt;++j) { for(int p=0;p<=len[j];++p) { Lagrange::y[p]=dp[j][p]; } Lagrange::init(e[i]-e[i-1]-1,len[j]); dp[j][0]=Lagrange::solve(e[i]-e[i-1]-1,len[j]); } } lim=1,pr=e[i]-1; memset(vis,false,sizeof(vis)); solve(1,n); for(int j=1;j<=cnt;++j) { dp[j][0]=dp[j][1]; } } } printf("%d\n",dp[id[1][n]][0]); return 0; }