[BZOJ4872] [Shoi2017]分手是祝愿

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

Sample Output
512

HINT

Source

单身狗才不存在什么分手不分手呢!(* ̄︿ ̄)
如何按开关才是最优的?
从大到小枚举如果当前的灯是亮的就关掉它,然后更新它的约数,这样的贪心最后次数是最少的并且是正确的(我也不知道为什么这样贪心就是对的)。
所以先预处理出来最少需要按多少次能结束游戏,如果这个次数小于等于kk就可以直接输出答案,如果大于kk我们就需要计算从当前到还剩kk个期望按几次
fif_{i}为从还剩ii次正确按法到还剩i1i-1次正确按法的期望次数,可以写出方程:fi=in+(1in)(1+fi+1+fi)f_{i}=\frac {i}{n} + (1-\frac{i}{n})(1+f_{i+1}+f_{i})
移项化解可得:

fi=n+niifi+1f_{i}= \frac{n+n-i}{i} \cdot f_{i+1}

因为要取模所以要求逆元,模为质数所以可以用费马小定理求逆元

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>

using namespace std;
typedef long long ll;
const int maxn=1e5+5;
const int mod=100003;

int a[maxn],n,k;
ll num,dp[maxn];

inline ll qpow(ll x,ll k)
{
ll ret=1;
while(k) {
if(k&1) ret=ret*x%mod;
x=x*x%mod;
k>>=1;
}
return ret;
}

inline ll inv(ll x) { return qpow(x,mod-2); } //费马小定理求逆元

inline ll getnum() //贪心求出当前最优方案
{
ll res=0;
for(int i=n;i!=0;--i)
if(a[i]) {
res++;
for(int j=1;j*j<=i;++j)
if(i%j==0) {
a[j]^=1;a[i/j]^=1;
if(j*j==i) a[j]^=1;
}
}
return res;
}

inline ll fact(ll x){ //求阶乘
for(int i=2;i<=n;++i) x=x*i%mod;
return x;
}

int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i) scanf("%d",a+i);
num=getnum();
//printf("%lld\n",num);
if(num<=k) { printf("%lld\n",fact(num));return 0; }
dp[n]=1;
for(int i=n-1;i>=k;--i)
dp[i]=1LL*(1LL*n+1LL*(n-i)*dp[i+1]%mod)*inv(i)%mod;
ll ans=0;
for(int i=num;i>k;--i) ans=(ans+dp[i])%mod;
ans=(ans+k)%mod;
printf("%lld\n",fact(ans));
return 0;
}