P1048 · OFFICIAL SOLUTION

P1048 风暴止息 官方题解

Gioush OJ · P1048 风暴止息

风暴止息

【题意简述】

nn 枚状态为 0011 的定风楔。启动控制阵式 ii 会翻转编号为 ii 的全部正约数。每一步等概率随机启动一个阵式;若当前局面可以在不超过 kk 次操作内恢复,就立即改用最少操作方案。求总操作次数的期望乘以 n!n! 后的值。

【Hint】

【提示】

从大到小可以唯一确定最少操作集合。随机操作只会使这个集合的大小增加或减少一,因此期望状态只需要记录集合大小。

【数据点 1∼51\sim 51∼5】

n20n\leq 20 时,可以把全部状态压成二进制数,每个控制阵式对应一个异或掩码。对接管范围外的状态列期望方程,接管范围内直接使用最少操作数作为边界。

这个做法说明,真正需要研究的是当前局面距离全零状态还差多少次最优操作。

【数据点 6∼106\sim 106∼10】

从编号 nn11 扫描。若第 ii 枚定风楔失稳,只有阵式 ii 能改变它;之后编号更小的操作也不会再次影响位置 ii。因此必须启动阵式 ii,并翻转 ii 的全部正约数。

每一步选择都是被当前最大失稳位置强制确定的,所以得到的最少操作集合唯一。直接枚举约数可以在 O(nn)\mathcal{O}(n\sqrt n) 时间求出集合大小。

【数据点 11∼1511\sim 1511∼15】

设唯一最少操作集合大小为 ss。随机选中集合内的阵式时,集合大小从 ss 变为 s1s-1;选中集合外阵式时,大小变为 s+1s+1。因此向下转移的概率为 sn\dfrac{s}{n},向上转移的概率为 nsn\dfrac{n-s}{n}

具体阵式编号已经不再影响随机过程,只需对状态数列期望方程。

【数据点 16∼1916\sim 1916∼19】

特殊性质保证初始最少操作数 sks\leq k,随机阶段不会开始。答案就是

sn!.s\cdot n!.

【正解】

eie_i 表示最少操作数从 ii 第一次变成 i1i-1 所需随机操作次数的期望。显然 en=1e_n=1

i<ni<n 时,以 in\dfrac{i}{n} 的概率一步到达 i1i-1;以 nin\dfrac{n-i}{n} 的概率先到达 i+1i+1,随后需要 ei+1e_{i+1} 回到 ii,再重新等待 eie_i。因此

ei=1+nin(ei+1+ei).e_i=1+\dfrac{n-i}{n}(e_{i+1}+e_i).

把自环项移到左侧,得到

ei=n+(ni)ei+1i.e_i=\dfrac{n+(n-i)e_{i+1}}{i}.

若初始最少操作数 sks\leq k,答案就是 ss。若 s>ks>k,随机阶段依次从 ss 降到 s1,,ks-1,\ldots,k,期望为

i=k+1sei.\sum_{i=k+1}^{s}e_i.

接管后还要执行 kk 次最优操作,所以总期望为

k+i=k+1sei.k+\sum_{i=k+1}^{s}e_i.

除法在模 998,244,353998{,}244{,}353 意义下乘逆元实现,最后再乘 n!n!

最少操作集合可以从大到小枚举位置。正式 Std 在必须选择阵式 ii 时,枚举 ii 的全部正约数并翻转对应位置。单次枚举为 O(i)\mathcal{O}(\sqrt i),最坏时间复杂度为 O(nn)\mathcal{O}(n\sqrt n)

也可以改为预处理每个编号的约数表,或从每个约数出发枚举倍数。后者的总枚举次数为调和级数

i=1nni=O(nlogn).\sum_{i=1}^{n}\left\lfloor\dfrac{n}{i}\right\rfloor =\mathcal{O}(n\log n).

【复杂度分析】

正式 Std 求最少操作集合的最坏时间复杂度为 O(nn)\mathcal{O}(n\sqrt n),逐项快速幂求逆元为 O(nlogMod)\mathcal{O}(n\log Mod)。总时间复杂度为 O(nn+nlogMod)\mathcal{O}(n\sqrt n+n\log Mod),空间复杂度为 O(n)\mathcal{O}(n)。若使用调和级数维护与线性递推逆元,可以把时间复杂度优化为 O(nlogn)\mathcal{O}(n\log n)

【参考代码】

/*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;}