P1048 风暴止息 官方题解
Gioush OJ · P1048 风暴止息
风暴止息
【题意简述】
有 枚状态为 或 的定风楔。启动控制阵式 会翻转编号为 的全部正约数。每一步等概率随机启动一个阵式;若当前局面可以在不超过 次操作内恢复,就立即改用最少操作方案。求总操作次数的期望乘以 后的值。
【Hint】
【提示】
从大到小可以唯一确定最少操作集合。随机操作只会使这个集合的大小增加或减少一,因此期望状态只需要记录集合大小。
【数据点 1∼51\sim 51∼5】
当 时,可以把全部状态压成二进制数,每个控制阵式对应一个异或掩码。对接管范围外的状态列期望方程,接管范围内直接使用最少操作数作为边界。
这个做法说明,真正需要研究的是当前局面距离全零状态还差多少次最优操作。
【数据点 6∼106\sim 106∼10】
从编号 向 扫描。若第 枚定风楔失稳,只有阵式 能改变它;之后编号更小的操作也不会再次影响位置 。因此必须启动阵式 ,并翻转 的全部正约数。
每一步选择都是被当前最大失稳位置强制确定的,所以得到的最少操作集合唯一。直接枚举约数可以在 时间求出集合大小。
【数据点 11∼1511\sim 1511∼15】
设唯一最少操作集合大小为 。随机选中集合内的阵式时,集合大小从 变为 ;选中集合外阵式时,大小变为 。因此向下转移的概率为 ,向上转移的概率为 。
具体阵式编号已经不再影响随机过程,只需对状态数列期望方程。
【数据点 16∼1916\sim 1916∼19】
特殊性质保证初始最少操作数 ,随机阶段不会开始。答案就是
【正解】
令 表示最少操作数从 第一次变成 所需随机操作次数的期望。显然 。
当 时,以 的概率一步到达 ;以 的概率先到达 ,随后需要 回到 ,再重新等待 。因此
把自环项移到左侧,得到
若初始最少操作数 ,答案就是 。若 ,随机阶段依次从 降到 ,期望为
接管后还要执行 次最优操作,所以总期望为
除法在模 意义下乘逆元实现,最后再乘 。
最少操作集合可以从大到小枚举位置。正式 Std 在必须选择阵式 时,枚举 的全部正约数并翻转对应位置。单次枚举为 ,最坏时间复杂度为 。
也可以改为预处理每个编号的约数表,或从每个约数出发枚举倍数。后者的总枚举次数为调和级数
【复杂度分析】
正式 Std 求最少操作集合的最坏时间复杂度为 ,逐项快速幂求逆元为 。总时间复杂度为 ,空间复杂度为 。若使用调和级数维护与线性递推逆元,可以把时间复杂度优化为 。
【参考代码】
/*Author:EhundateghDate:2026/7/30Name:calm.cppYou steal,I kill.*/#include <cmath>#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 100010using namespace std;const int Mod=998244353;inline int Add(int a,int b){return a+b>=Mod?a+b-Mod:a+b;}inline int Del(int a,int b){return a-b<0?a-b+Mod:a-b;}inline int Mul(int a,int b){return 1ll*a*b%Mod;}int c,T,n,k,cnt,Fact,Dp[MAXN],Ans;int Cond[MAXN];inline int Inv(int a){ int b=Mod-2,Ret=1; while(b){ if(b&1) Ret=Mul(Ret,a); a=Mul(a,a); b>>=1; } return Ret;} void Solve(){ scanf("%d%d",&n,&k); cnt=Ans=0; Fact=1; for(int i=1;i<=n;i++) scanf("%d",&Cond[i]),Fact=Mul(Fact,i); for(int i=n;i>=1;i--){ if(Cond[i]){ for(int j=1;j*j<=i;j++){ if(i%j==0){ Cond[j]^=1; if(j*j!=i) Cond[i/j]^=1; } } cnt++; Cond[i]=0; } } Dp[n]=1; if(cnt<=k){ printf("%d\n",Mul(cnt,Fact)); return; } for(int i=n-1;i>=1;i--) Dp[i]=Mul(Inv(i),Add(n,Mul(n-i,Dp[i+1]))); for(int i=cnt;i>=k+1;i--) Ans=Add(Ans,Dp[i]); Ans=Mul(Add(Ans,k),Fact); printf("%d\n",Ans);} int main(){ scanf("%d%d",&c,&T); while(T-->0) Solve(); return 0;}