【洛谷】P3750 [六省联考 2017] 分手是祝愿
题意
B 君在玩一个游戏,这个游戏由 \(n\) 个灯和 \(n\) 个开关组成,给定这 \(n\) 个灯的初始状态,下标为从 \(1\) 到 \(n\) 的正整数。
每个灯有两个状态亮和灭,我们用 \(1\) 来表示这个灯是亮的,用 \(0\) 表示这个灯是灭的,游戏的目标是使所有灯都灭掉。
但是当操作第 \(i\) 个开关时,所有编号为 \(i\) 的约数(包括 \(1\) 和 \(i\))的灯的状态都会被改变,即从亮变成灭,或者是从灭变成亮。
如果当前局面,可以通过操作小于等于 \(k\) 个开关使所有灯都灭掉,那么他将不再随机,直接选择操作次数最小的操作方法(这个策略显然小于等于 \(k\) 步)操作这些开关。
B 君想知道按照这个策略(也就是先随机操作,最后小于等于 \(k\) 步,使用操作次数最小的操作方法)的操作次数的期望。
输出期望乘以 \(n\) 的阶乘对 \(100003\) 取模之后的结果。
\(1 \leq n \leq 10^5, 0 \leq k \leq n\)。
思路
虽然灭掉当前节点会影响其他的节点,但不难发现,每个节点无法通过其他的操作组合来取代掉。于是可以考虑从大到小遍历,如果当前灯亮着,那么就把它灭掉,这样做所需的操作次数一定是最少的,且这些编号的灯一定要被直接灭掉。记这样需要的操作次数为 \(m\)。
那么当 \(m \leq k\) 时,只需要 \(m\) 步就可以使所有灯灭掉。
而当 \(m > k\) 时,设 \(f_i\) 为从需要 \(i\) 次操作到需要 \(i-1\) 次操作的期望操作次数,那么有 \(\frac{i}{n}\) 的概率选到需要的节点,而有 \(\frac{n-i}{n}\) 的概率选错节点,此时选错的那个节点就还需要额外再按一次。可以得到转移方程:
化简可以得到:
那么最终总的期望次数就是 \(k+\sum_{i=k+1}^{m} f_i\)。
code:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5+10,mod=100003;
int f[N],ans,n,k,tot; bool state[N];
int mul(int a,int b){int res=1;while(b){if(b&1) res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}return res;}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&state[i]);
for(int i=n;i>=1;i--)
if(state[i])
{
tot++;
for(int j=1;j*j<=i;j++)
{
if(i%j) continue;
state[j]^=1;if(j*j!=i) state[i/j]^=1;
}
}
for(int i=n;i>=1;i--) f[i]=1ll*(n+1ll*(n-i)*f[i+1]%mod)%mod*mul(i,mod-2)%mod;
//需要注意f从n开始递推,而不是tot,因为在tot时也有概率按掉不应该按的灯,使需要的操作次数大于tot
if(tot<=k) ans=tot;
else
{
for(int i=tot;i>k;i--) ans=(ans+f[i])%mod;
ans=(ans+k)%mod;
}
for(int i=1;i<=n;i++) ans=1ll*ans*i%mod;
printf("%d\n",ans);
return 0;
}