bzoj4872 [Shoi2017]分手是祝愿 (期望概率DP)

来源:互联网 发布:跳跃网络ceo吴亚西 编辑:程序博客网 时间:2024/04/26 23:56

bzoj4872 [Shoi2017]分手是祝愿

原题地址:http://www.lydsy.com/JudgeOnline/problem.php?id=4872

题意:
给出n个灯和它们初始的开关状态,每次操作若选择灯i,则所有i的约数都改变状态(包括i和1)。我们要通过操作把全部灯关掉。每次随机选择一个灯,如果当前最优策略操作数≤k直接用最优策略。问期望操作数*n! %100003的值。

数据范围
1 ≤ n ≤ 100000, 0 ≤ k ≤ n
%50的数据满足k==n。

题解:
因为只想到需要表示状态的DP没想到还有这种操作…
这个k==n的部分数据是一个提示:
在这种情况下,最优方案就是从大到小,如果是亮的就对他操作。
为什么? 最大的亮的灯如果不操作它本身,只能被其倍数操作,而其倍数就是多按的还要弄灭,以此类推,只会增加多余操作,最后还是要按这个灯本身。
故,对于一个开关状态,其最优方案是固定的唯一的。

于是产生了我们的状态定义:
f[i]表示最优方案是按i个灯 到 最优方案是按i-1个灯的期望操作数。
因为这i个灯是固定的,所以有i/n的概率按对,就直接转过来了,
(n-i)/n的概率按错,赚到了f[i+1]
于是:
f[i]=in+nin(1+f[i+1]+f[i])
移项:
f[i]=(n+(ni)f[i+1])i
发现f[n]=1,于是可以直接推下来,
若给出的状态本来的最优方案是cnt
答案就是,k+cnti=k+1f[i]


其实一般不好想到DP维护这个差值,
一般的想法dp[i]表示从 最优方案是按i个灯 到 没有灯的期望操作,
dp[i]=indp[i1]+nindp[i+1]+1
这东西…
初始化就是f[k]=kf[i]=f[i1]+1
但是发现移项可以得到更简便的东西,
于是才想到这个差分数组f。


猜原来可能是模任意数,所以才要乘n!,刚好对应式子里的分母,这样就不用担心逆元的问题。
结果100003是个质数。


代码:

#include<cstdio>#include<iostream>#include<cstring>#include<algorithm>using namespace std;const int mod=100003;const int N=100005;int n,k,inv[N],a[N],ans=0;int f[N],cnt=0;int main(){    scanf("%d%d",&n,&k);    for(int i=1;i<=n;i++) scanf("%d",&a[i]);    inv[0]=1; inv[1]=1;     for(int i=2;i<=n;i++) inv[i]=1LL*(mod-(mod/i))*inv[mod%i]%mod;    for(int i=n;i>=1;i--) if(a[i])    {        cnt++;        for(int j=1;j*j<=i;j++)         if(i%j==0) { a[j]^=1; if(i/j!=j) a[i/j]^=1; }    }    if(cnt<=k) ans=cnt;    else    {        f[n+1]=0; f[n]=1;        for(int i=n-1;i>k;i--) f[i]=1LL*inv[i%mod]*((n+1LL*(n-i)*f[i+1]%mod)%mod)%mod;        for(int i=cnt;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;}