[题解] P5888 传球游戏
一道非常好的 dp 和理解滚动数组的题
理解题意(做题即翻译)
有 n 个人摆成环形在玩传球游戏,每个人可以传给任意编号的人,但是某个 zz 觉得太简单了,于是加了 k 条限制:其格式为 u 不可传球到 v 这个人,问 m 次传球后回到 1 的方案数是多少。
数据范围很重要
n <= 1e9 , k <= 5e4。
睁眼一看,我靠n怎么这么大,这要O(n)???!!!
状态选择
明显地,最好选择传了 i 次球为状态,考虑求在第 j 个人的手里的方案数是
然而这会爆空间,因此本题的亮点之一滚动数组就派上用场了,我们在状态转移的时候再谈。
状态转移
我们发现 n 很大,但是 k 却不是很大,回到题意,也就是说受限制的人很少,设受限制的人(称为特殊人)为 k 个(包括1号),则对于其余 n - k 个人都是一样的,所以我们把 0 号看成一般人,1-k 号看成特殊人进行状态转移。
转移方法:
思路有了,方法也就差不多出来了。
接着考虑进一步优化:
对于sigma我们可以在上一层状态求一个 sum 来存储上一层的状态有多少特殊人传给 j 人的方案,然后加上 0 号人的,减去非法方案更新就完了。
至于滚动数组,我们可以吧f数组变成
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cstdlib>
#define re register
using namespace std;
const int P = 998244353;
const int maxn=1e6+5,maxm=5e4+5;
int n,m,k;
int a[maxm],b[maxm],read[maxn],cnt=0,head[maxn],s[maxn];
long long f[2][maxn];
int num = 0;
struct node{
int to,nxt;
}e[maxm];
inline void link(int u,int v){
e[++cnt].to=v;e[cnt].nxt=head[u];head[u]=cnt;
}
int main(){
scanf("%d%d%d",&n,&m,&k);
for(re int i=1;i<=k;i++){
scanf("%d%d",a+i,b+i);
if(a[i]==b[i])continue;//需要判断
read[++num]=a[i];
read[++num]=b[i];
}
read[++num]=1;//1也算,他不可以随便传球
sort(read+1,read+num+1);
int Cnt=0;
for(re int i=1;i<=num;i++){
if(i==1 || read[i]!=read[i-1])s[++Cnt]=read[i];
}
for(re int i=1;i<=k;i++){//离散化
a[i]=lower_bound(s+1,s+1+Cnt,a[i])-s;
b[i]=lower_bound(s+1,s+1+Cnt,b[i])-s;
if(a[i]==b[i])continue;
link(b[i],a[i]);
}
f[0][1] = 1;
k = Cnt;//特殊人的个数
long long sum = 1;
for(re int i=1;i<=m;i++){//状态
for(re int j=0;j<=k;j++)
f[1][j]=(sum+1LL*(n-k)*f[0][0]%P)%P;
sum=0;
for(re int j=0;j<=k;j++){//决策
for(re int k=head[j];k;k=e[k].nxt){//遍历不能传给他的人(反向建图的好处),减去非法方案,避免出现负数!!!
int v=e[k].to;
f[1][j]=(f[1][j]-f[0][v]+P)%P;
}
f[1][j]=(f[1][j]-f[0][j]+P)%P;//自己不能传给自己
if(j)sum=(sum+f[1][j])%P;
}
for(re int j=0;j<=k;j++){//滚动数组,为下一层状态做准备
f[0][j]=f[1][j];
f[1][j]=0;
}
}
cout<<f[0][1];
return 0;
}
PS:对于离散化操作,我们可以把所有特殊人存到一个有序的辅助空间 s 里,将其下标作为建图的下标即可
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~