[六省联考2017]分手是祝愿
发现无论是什么时候,毕姥爷的概率题我就是不会做
首先先看到一个显然的性质,就是一个开关最多被操作一次,于是整个序列最多也就被操作\(n\)次
看到有\(50\)分\(k=n\),于是只需要求一下最少几步关掉全部的灯即可
这里需要一个贪心,显然我们需要先去关编号大的灯,编号大的灯只会影响它的约数不会影响编号更大的灯,这样贪心可以保证每个开关只会被操作一次
调和级数存一下约数,暴力一下就做完了,这样就有\(80\)分了
我们考虑我们随机操作有什么用处
假设我们求出来原来最优需要\(x\)步就能全部关掉,那么我们随机操作到的可能恰好是我们需要关掉的,操作之后就只需要\(x-1\)步了,如果操作到的是一个原来不需要关掉的,我们就必须额外操作一次,就需要\(x+1\)步了
所以实际上这个问题的具体的开关状态并没有什么关系,只和当前最有策略需要多少步有关系
设\(dp_i\)表示到\(i\)步这个状态期望经过多少次
显然有
初值\(dp_x=1\)
很好理解,\(dp_{i+1}\)这个状态有\(\frac{i+1}{n}\)的概率选择到一个需要操作的灯转移到\(dp_i\),\(dp_{i-1}\)同理
可以直接高斯消元,但是复杂度\(O(n^3)\)显然不科学
于是我就不会了,本以为这个柿子有奇妙的性质,但是无论怎么化都发现这是一个需要高斯消元的垃圾柿子
于是抄题解
换个角度考虑,设\(f_i\)表示从\(i\)这个状态转移到\(i-1\)的期望次数
有\(\frac{i}{n}\)的概率直接转移过去,这里的消耗是\(\frac{i}{n}\)
有\(\frac{n-i}{n}\)的概率转移到\(i+1\),这里的消耗是\(\frac{n-i}{n}\),之后得从\(i+1\)转移到\(i-1\),需要自然是\(f_{i+1}+f_{i}\)
于是
随便化一化,发现
可以直接递推了
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#define maxn 100005
#define re register
#define LL long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
const LL mod=100003;
inline int read() {
int x=0;char c=getchar();while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
void exgcd(LL a,LL b,LL &x,LL &y) {if(!b) {x=1,y=0;return;}exgcd(b,a%b,y,x);y-=a/b*x;}
inline LL inv(LL a) {LL x,y;exgcd(a,mod,x,y);return (x%mod+mod)%mod;}
LL fac;
int a[maxn],n,m;
int now;
std::vector<int> v[maxn];
LL dp[maxn];
int main() {
n=read(),m=read();
fac=1;for(re int i=1;i<=n;i++) fac=(fac*i)%mod;
for(re int i=1;i<=n;i++)
for(re int j=i;j<=n;j+=i) v[j].push_back(i);
for(re int i=1;i<=n;i++) a[i]=read();
for(re int i=n;i;--i) {
if(!a[i]) continue;
now++;
for(re int j=0;j<v[i].size();j++) a[v[i][j]]^=1;
}
if(now<=m) {printf("%lld\n",(LL)now*fac%mod);return 0;}
dp[n]=1;
for(re int i=n-1;i>=1;--i) dp[i]=1ll+(dp[i+1]+1ll)*n%mod*inv(i)%mod-(dp[i+1]+1ll),dp[i]=(dp[i]+mod)%mod;
LL ans=m;
for(re int i=now;i>m;--i) ans=(ans+dp[i])%mod;
printf("%lld\n",ans*fac%mod);
return 0;
}