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)\)

posted @ 2024-09-28 22:20  spdarkle  阅读(9)  评论(0编辑  收藏  举报