[BZOJ 4872][SHOI&SXOI2017]分手是祝愿(期望Dp)
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 取模之后的结果。
Solution
最优解:从大到小遇到开着的灯就进行操作
出题人良心,考试的时候骗到70…(本来可以是80?)
期望只和操作次数有关,当前最少需要i步操作时,通过一次操作只可能使需要的操作加一或减一
设f[i]为还差i步的期望,如果i<=k,那么f[i]=i,否则f[i]=(f[i-1]*i+f[i+1]*(n-i))/n+1
设g[i]=f[i]-f[i-1],如果i<=k,那么g[i]=1否则g[i]=g[i+1]*(n-i)/i+n/i
特别注意到f[n]=f[n-1]+1即g[n]=1,可以直接依次算出所有的g
——抄的(PPT)
脑子卡带,g[i]的那个式子我居然推了半天…QvQ
这里写下过程
g[i]=(f[i-1]*i+f[i+1]*(n-i))/n+1-f[i-1]
=(f[i+1]*(n-i)-f[i-1]*(n-i))/n+1
=(n-i)*(f[i+1]-f[i-1])/n+1
=(n-i)*(g[i+1]+g[i])/n+1
所以g[i]=g[i+1]*(n-i)/i+n/i
【语文差数学也差的人还有没有活路了
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #define Mod 100003 #define MAXN 100005 using namespace std; typedef long long LL; int n,k,a[MAXN]; LL g[MAXN],f[MAXN]; LL exgcd(int a,int b,LL &d,LL &x,LL &y) { if(!b){d=1,x=1,y=0;return a;} else {exgcd(b,a%b,d,y,x);y-=x*(a/b);} } LL inv(LL a) { LL d,x,y; exgcd(a,Mod,d,x,y); return (x+Mod)%Mod; } int main() { scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) scanf("%d",&a[i]); int cnt=0; for(int j=n;j>0;j--) { if(!a[j])continue; cnt++; for(int i=1;i*i<=j;i++) { if(j%i==0) { a[i]^=1; if(i*i!=j)a[j/i]^=1; } } } for(int i=1;i<=k;i++)f[i]=i; g[n]=1; for(int i=n;i>k;i--) g[i]=(n+(n-i)*g[i+1]%Mod)%Mod*inv(i)%Mod; for(int i=k+1;i<=cnt;i++) f[i]=(g[i]+f[i-1])%Mod; LL ans=1; for(int i=1;i<=n;i++) ans=(ans*i)%Mod; ans=(ans*f[cnt])%Mod; printf("%lld\n",ans); return 0; }