您的位置:首页 > 其它

[bzoj4872][Shoi2017]分手是祝愿

2017-05-22 20:57 169 查看
来自FallDream的博客,未经允许,请勿转载,谢谢。

B 君在玩一个游戏,这个游戏由 n 个灯和 n 个开关组成,给定这 n 个灯的初始状态,下标为从 1 到 n 的正整数。每个灯有两个状态亮和灭,我们用 1 来表示这个灯是亮的,用 0 表示这个灯是灭的,游戏的目标是使所有灯都灭掉。但是当操作第 i 个开关时,所有编号为 i 的约数(包括 1 和 i)的灯的状态都会被改变,即从亮变成灭,或者是从灭变成亮。B 君发现这个游戏很难,于是想到了这样的一个策略,每次等概率随机操作一个开关,直到所有灯都灭掉。这个策略需要的操作次数很多, B 君想到这样的一个优化。如果当前局面,可以通过操作小于等于 k 个开关使所有灯都灭掉,那么他将不再随机,直接选择操作次数最小的操作方法(这个策略显然小于等于 k 步)操作这些开关。B 君想知道按照这个策略(也就是先随机操作,最后小于等于 k 步,使用操作次数最小的操作方法)的操作次数的期望。这个期望可能很大,但是 B 君发现这个期望乘以 n 的阶乘一定是整数,所以他只需要知道这个整数对 100003 取模之后的结果。
n<=100000

题目名剧毒
首先发现关掉灯的方案是唯一的 因为每个都只和比他大的有关系,所以从大到小一一确定即可,复杂度nlogn
所以这道题首先暴力找出有几个要改,如果小于k,输出k乘以n! 就好了 这个居然就有80分。

/以下是瞎搞
剩下的很容易想到一个dp,f[i][j]表示走i步有j个要改的方案,这样每个i转移是O(n)的
后面的贡献非常小 longdouble统计答案,多转移几次啥的说不定能拿到一点分数?
(确实成功拿到了5分)
以上是瞎搞/

用f[i]表示从有i个要改的状态出发,走到f[i-1]这个状态的期望次数,有
$f[i]=\frac{n-i}{n}*(f[i]+f[i+1])+1$
整理得到$f[i]=\frac{n+(n-i)f[i+1]}{i}$
递推即可,边界f
=1 复杂度O(n)
答案是$\sum_{i=k+1}^{p}f[i] * n!$ 其中p是一开始要改的数量。
复杂度nlogn

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#define MN 100000
#define mod 100003
#define ld long double
using namespace std;
inline int read()
{
int x = 0 , f = 1; char ch = getchar();
while(ch < '0' || ch > '9'){ if(ch == '-') f = -1;  ch = getchar();}
while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
return x * f;
}

vector<int> v[MN+5];
int n,k,s[MN+5],sum=0,inv[MN+5],f[MN+5],ans=0;

int main()
{
n=read();ans=k=read();
for(int i=1;i<=n;++i) s[i]=read();
for(int i=1;i<=n;++i)
for(int j=i;j<=n;j+=i) v[j].push_back(i);
for(int i=n;i;--i)
if(s[i])
{
s[i]=0,++sum;
for(int j=0;j<v[i].size();++j)
s[v[i][j]]^=1;
}
if(sum<=k)
{
for(int i=1;i<=n;++i) sum=1LL*sum*i%mod;
return 0*printf("%d\n",sum);
}
inv[0]=inv[1]=f
=1;
for(int i=2;i<=n;++i) inv[i]=1LL*(mod-mod/i)*inv[mod%i]%mod;
for(int i=n-1;i>k;--i)
f[i]=1LL*(n+1LL*(n-i)*f[i+1]%mod)*inv[i]%mod;
for(int i=sum;i>k;--i) (ans+=f[i])%=mod;
for(int i=1;i<=n;++i) ans=1LL*ans*i%mod;
printf("%d\n",ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: