[bzoj4872]分手是祝愿
Description
Zeit und Raum trennen dich und mich.
时空将你我分开。B 君在玩一个游戏,这个游戏由 n 个灯和 n 个开关组成,给定这 n 个灯的初始状态,下标为
从 1 到 n 的正整数。每个灯有两个状态亮和灭,我们用 1 来表示这个灯是亮的,用 0 表示这个灯是灭的,游戏
的目标是使所有灯都灭掉。但是当操作第 i 个开关时,所有编号为 i 的约数(包括 1 和 i)的灯的状态都会被
改变,即从亮变成灭,或者是从灭变成亮。B 君发现这个游戏很难,于是想到了这样的一个策略,每次等概率随机
操作一个开关,直到所有灯都灭掉。这个策略需要的操作次数很多, B 君想到这样的一个优化。如果当前局面,
可以通过操作小于等于 k 个开关使所有灯都灭掉,那么他将不再随机,直接选择操作次数最小的操作方法(这个
策略显然小于等于 k 步)操作这些开关。B 君想知道按照这个策略(也就是先随机操作,最后小于等于 k 步,使
用操作次数最小的操作方法)的操作次数的期望。这个期望可能很大,但是 B 君发现这个期望乘以 n 的阶乘一定
是整数,所以他只需要知道这个整数对 100003 取模之后的结果。
Input
第一行两个整数 n, k。
接下来一行 n 个整数,每个整数是 0 或者 1,其中第 i 个整数表示第 i 个灯的初始情况。
1 ≤ n ≤ 100000, 0 ≤ k ≤ n;
Output
输出一行,为操作次数的期望乘以 n 的阶乘对 100003 取模之后的结果。
Sample Input
4 0
0 0 1 1
0 0 1 1
Sample Output
512
题解:
熟悉的题目啊...(17年省选打酱油的回忆)这个题的灵魂就是期望式子的推导
我们先来考虑不随机的情况:
我们应该从大编号往小编号(因为大编号只能按自己才能改变)一路按过去
这样模拟一遍我们就可以算出来正常情况下要按多少次,设这个次数为num
那么,如果考虑等概率瞎按的情况呢?
我们设f[i]为从"剩下i个位置亮按到到剩下i-1个位置亮"需要按的次数
那么有2种情况
1°i/n的概率按到一个应该按的灯,直接成功
2°(n-i)/n的概率按到一个不应该按的灯,这时先要按回来到i,再按到i-1
所以f[i]=i/n+(n-i)/n*(b[i+1]+b[i]+1),再给它大力化简一下,把f[i]放在一边
得到:f[i]=(f[i+1]*(n-i)+n)/i
求出来之后,我们就要统计(k,num]间num的累加之和,再乘上阶乘就完事了,ans=Σ{f[i],i∈(k,num]}*n!
如果n==k或者k>num,一上来就直接最优解,不用随机,这时ans=num*n!
在计算的时候处理上取模(逆元之类的),就可以A掉这道题了
代码见下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<cstring> #include<cstdio> using namespace std; typedef long long LL; const int N=100010; const int mod=100003; int n,k,a[N]; LL f[N],num; LL quick_mod(LL di,int mi) { LL ret=1; while(mi) { if(mi&1)ret=ret*di%mod; di=di*di%mod; mi>>=1; } return ret; } int main() { scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) scanf("%d",&a[i]); for(int i=n;i>=1;i--) if(a[i]) { for(int j=1;j*j<=i;j++) if(!(i%j)) { a[j]^=1; if(j*j!=i)a[i/j]^=1; } num++; } for(LL i=n;i>=1;i--) f[i]=(f[i+1]*(n-i)%mod+n)%mod*quick_mod(i,mod-2)%mod; LL t=0; if(num<k||n==k)t=num; else { for(LL i=num;i>k;i--) t=(t+f[i])%mod; t=(t+k)%mod; } for(LL i=1;i<=n;i++) t=t*i%mod; printf("%lld",t); }
Progress is not created by contented people.