[六省联考2017]分手是祝愿

题目

发现无论是什么时候,毕姥爷的概率题我就是不会做

首先先看到一个显然的性质,就是一个开关最多被操作一次,于是整个序列最多也就被操作\(n\)

看到有\(50\)\(k=n\),于是只需要求一下最少几步关掉全部的灯即可

这里需要一个贪心,显然我们需要先去关编号大的灯,编号大的灯只会影响它的约数不会影响编号更大的灯,这样贪心可以保证每个开关只会被操作一次

调和级数存一下约数,暴力一下就做完了,这样就有\(80\)分了

我们考虑我们随机操作有什么用处

假设我们求出来原来最优需要\(x\)步就能全部关掉,那么我们随机操作到的可能恰好是我们需要关掉的,操作之后就只需要\(x-1\)步了,如果操作到的是一个原来不需要关掉的,我们就必须额外操作一次,就需要\(x+1\)步了

所以实际上这个问题的具体的开关状态并没有什么关系,只和当前最有策略需要多少步有关系

\(dp_i\)表示到\(i\)步这个状态期望经过多少次

显然有

\[dp_i=\frac{(i+1)dp_{i+1}}{n}+\frac{(n-i+1)dp_{i-1}}{n} \]

初值\(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}\)

于是

\[f_i=1+\frac{(n-i)(f_i+f_{i+1})}{n} \]

随便化一化,发现

\[f_i=\frac{f_{i+1}\times n}{i}-f_{i+1} \]

可以直接递推了

#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;
}
posted @ 2019-02-20 20:34  asuldb  阅读(280)  评论(1编辑  收藏  举报