[CF2018F] Speedbreaker Counting 题解

这里是三道题。

这场比赛的 B 题是本题的前置题,他告诉我们:

“假如当前区间右侧存在一个 \(k\),使得我们再不往右走就无法占领 \(k\),就向右走,否则向左。”是一种最优决策。

他甚至还慷慨地告诉我们:

最终的合法起始点集合要么为 \(x\in\bigcap\limits_{i=1}^n[i-a_i+1,i+a_i-1]\),要么为 \(\emptyset\)

有了这两个定论,我们就可以开始正解的思考了。

\(f_{i,j}\) 表示答案区间为 \([i,j]\) 的方案数。考虑容斥。设 \(g_{i,j,0/1}\) 表示我们现在区间已经拓宽到了 \([i,j]\),下一步要向左或右走的方案数。则有:

\[g_{i,j,1}=(n-j+i)\times(g_{i+1,j,0}+g_{i,j-1,1}) \]

\[g_{i,j,0}=(n-j+i)\times g_{i+1,j,0}+g_{i,j-1,1} \]

假如我们现在要求解 \(f_{l,r}\),那么我们设初值 \(g_{l,r,0}=g_{l,r,1}=1\),则有:

\[f_{l,r}=g_{1,n,0}\times\prod\limits_{i=l}^r(n-\max(i-l,r-i)) \]

由于对于每一个 \(f_{l,r}\) 都要跑一遍 \(O(n^2)\ dp\),所以导致时间复杂度劣到了 \(O(n^4)\),连 F2 都过不了,考虑优化。

考虑一些神奇的 trick。发现这些 \(dp\) 终点状态相同(\(g_{1,n,0}\)),转移方程相同,于是我们从 \(g_{1,n,0}\) 开始倒着推回去,就可以只用一次 \(dp\) 解决问题。时间复杂度 \(O(n^2)\)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=3005;
int t,n,p,h[N],g[N][N][2],f[N][N],ans[N];
void solve(){
	cin>>n>>p,h[1]=1;
	for(int i=1;i<=n;h[++i]=1){
		for(int j=i;j<=n+1;j++)
			g[i][j][0]=g[i][j][1]=f[i][j]=0;
		for(int j=1;j<=i;j++)
			h[i]=(ll)h[i]*(n-max(j-1,i-j))%p; 
	}g[1][n][0]=1,ans[n]=0;
	for(int ln=n;ln;ans[--ln]=0)
		for(int i=1,j=ln;j<=n;i++,j++){
			f[i][j]=(ll)(g[i][j][0]+g[i][j][1])*h[ln]%p;
			g[i+1][j][0]=(g[i+1][j][0]+(ll)(g[i][j][0]+g[i][j][1])*(n-j+i))%p;
			g[i][j-1][1]=(g[i][j-1][1]+g[i][j][0]+(ll)g[i][j][1]*(n-j+i))%p;
			ans[ln]=((ll)ans[ln]+f[i][j]-f[i-1][j]-f[i][j+1]+f[i-1][j+1]+p+p)%p;
		}ans[0]=1;
	for(int i=1;i<=n;i++) ans[0]=(ll)ans[0]*n%p;
	for(int i=1;i<=n;i++) ans[0]=(ans[0]-ans[i]+p)%p;
	for(int i=0;i<=n;i++) cout<<ans[i]<<" ";cout<<"\n";
}int main(){
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>t;while(t--) solve();
	return 0;
}
posted @   长安一片月_22  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~
点击右上角即可分享
微信分享提示