CF2018F1~3 Solution
相当于计数如下正整数序列 \(a\) 的个数:
- \(\forall i\in [1,n],a_i\in [1,n]\)
- \(\min_i(i+a_i-1)-\max_i(i-a_i+1)=k\)
- \(\forall i\in [1,n]\),\(\max_{a_j\le i}j-\min_{a_j\le i}j+1\le i\)
我们可以考虑枚举 \(\min(i+a_i-1)\),这样通过 \(O(n)\) 的代价确定了每个 \(a_i\) 的范围。
那么就只需要考虑第三个条件了,同时需要注意 \(a\) 需要顶到上界。
感觉可以按值大小依次填数唉。
如果按照原题目的想法,也可以看作答案区间 \([l,r]\) 需要合法的扩展到并且可以扩展到全局。
哦,要计算所有的 \(k\) 啊。
那相当于是我可以考虑容斥,也就是我计算出 \(ans_{l,r}\) 保证区间 \([l,r]\) 内的点合法的话,也就是至少,那么恰好 \(ans'_{l,r}\) 也就是 \(ans_{l,r}\) 减去所有包含他的区间的 \(ans'\),这一步显然可以 \(O(n^2)\) 推出来。
那么我们考虑如何求出 \(ans_{l,r}\)。
考虑使用这样一种策略:除非右侧有一个点需要立刻向右走,否则就向左走,这样可以确保一个起点生成的方案唯一。
我们将一个方案描述为 \((a,p)\) 表示数组 \(a\) 和访问顺序 \(p\)。
根据 \(i+a_i-1\ge r,i-a_i+1\le l\implies a_i\ge \max(r+1-i,i+1-l)\)
定义 \(dp_{l,r,0/1}\) 为已经访问完了区间 \([l,r]\) 当前有没有一个强制要求往右走的限制。
初始值显然是 \(dp_{i,i,0}=dp_{i,i,1}=[i\in [l,r]](n+1-\max(r+i-i,i+1-l))\)
考虑转移。
注意到如果你从 \([l,r]\) 往外扩展一个位置,则它是第 \(r-l+2\) 个访问的,则它的 \(a\) 值必须满足 \(\ge \max(r-l+2,r-i+1,i-l+1)\)
而且如果你填了 \(r-l+2\) 也可以选择消去必须往右走的限制。
同时每一次扩展后导致长度增加,那么也有可能会导致又必须往右走。
所以可以设计出一个 \(O(n^2)\) 每次的DP。
另外,对于一个合法的 \(a\) 其所有起始位置任意,所以需要除掉 \(r-l+1\)。
所以会有一个 \(O(n^2)\) 枚举 \(l,r\),再 \(O(n^2)\) DP,求得 \(ans_{l,r}\) 为钦定 \([l,r]\) 内点为答案的 \(O(n^4)\) 算法。
那么容斥下就是 \(ans_{l,r}+ans_{l-1,r+1}-ans_{l-1,r}-ans_{l,r+1}\) 为真正答案。
另外 \(k=0\) 的答案用 \(n^n\) 减掉其他即可。
int sol(int L,int R){
for(int i=1;i<=n;++i)for(int j=i;j<=n;++j)f[i][j][0]=f[i][j][1]=0;
for(int i=L;i<=R;++i)f[i][i][0]=f[i][i][1]=n+1-max(R-i+1,i-L+1);
for(int len=2;len<=n;++len){
for(int l=1,r=len;r<=n;++l,++r){
int vl=(n+1-Max({R-l+1,l-L+1,len}));
int vr=(n+1-Max({R-r+1,r-L+1,len}));
f[l][r][0]=f[l+1][r][0]*vl%p;
if(len>=max(R-r+1,r-L+1))(f[l][r][0]+=f[l][r-1][1])%=p;
f[l][r][1]=(f[l+1][r][0]*vl%p+f[l][r-1][1]*vr%p)%p;
}
}
// cout<<f[1][n][0]<<"\n";
return f[1][n][0];
}
写出代码后可以发现,对于相同长度的区间,\(L,R\) 的变化是相同的,且贡献次数与DP过程中的 \(l,r\) 与 \(L,R\) 的差量有关,而 \(len\) 在哪里都是常量,所以可以考虑将 \(L,R\) 的移动替代为 \(l,r\) 的移动。
也即,我们只处理 \(sol(n-len+1,n)\),但是\(l,r\) 的取值是 \([1,2n]\),这样借助内部DP端点的移动替代了 \(L,R\) 的移动,则相同长度的DP只需要跑一次,就可以做到 \(O(n^3)\) 了。
for(int len=1;len<=n;++len){
sol(n-len+1,n);
for(int l=1,r=len;r<=n;++r,++l)ans[l][r]=f[l][n+l-1][0]*inv[len]%p;
}
注意到 F3 需要一个 \(O(n^2)\) 的DP。
我们注意到 一个方案中,第一次往右走的时刻就确定了左端点,当然反过来也是一样的。
所以如果我们可以考虑求一个固定左端点的DP。
也即如果我们可以求得 \(f_l\) 表示所有合法区间左端点 \(\le l\) 的方案数之和,则 \(k\) 的答案就是 \(f_{n-k+1}-f_{n-k}\)
我们对于每个 \(l\) 进行倒序DP,初始化 \(dp_{1,n,0}=1\),不记录 \(r\),那么 \([l,r]\) 的答案就是 \(dp_{r,r,1}\)
可以运用上面那个技巧将 \(r\) 这个维度压掉,这样算 \(dp_{r,r,1}\) 后需要乘上一个 \(r\)
这样就做到了 \(O(n^2)\)