[六省联考2017]分手是祝愿(期望DP)

[六省联考2017]分手是祝愿(期望DP)

题面

分析

先考虑初始状态灭掉所有灯的最小次数\(cnt\),可以从大到小贪心求出。因为每个灯只能被比它大的更新,可以直接从大到小,遇到开的就按灭,同时操作它的约数。复杂度\(O(n\log n)\)

如果\(cnt \leq k\),那么只需要初始时按最小次数操作即可,输出\(n!\cdot cnt\)
否则需要期望DP.容易发现,每个开关都不能被其他开关的组合替代。设\(f_i\)表示把需要按\(i\)个正确的开关(可能有重复)来灭掉所有灯的状态转化为需要按\(i-1\)个正确的开关的期望次数。那么有:

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

因为随机选有可能操作\(i\)个正确的开关,也可能操作错误的开关,那么就需要再按一次抵消这次操作,需要的开关数变成\(i+1\),再变回\(i\),因此加上\(f_i+f_{i+1}\)

移项得到递推方程,边界\(f_n=1\)

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

最终答案\(n!(k+\sum_{j=k+1}^{cnt} f_j)\)

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#define mod 100003
#define maxn 100000
using namespace std;
typedef long long ll;
inline ll fast_pow(ll x,ll k){
	ll ans=1;
	while(k){
		if(k&1) ans=ans*x%mod;
		x=x*x%mod;
		k>>=1;
	} 
	return ans;
} 
inline ll inv(ll x){
	return fast_pow(x,mod-2);
}
int n,k;
vector<int>d[maxn+5]; 
int a[maxn+5];

ll fact[maxn+5],invfact[maxn+5],invx[maxn+5];
ll dp[maxn+5];
void ini(int n){
	fact[0]=1;
	for(int i=1;i<=n;i++) fact[i]=fact[i-1]*i%mod;
	invfact[n]=inv(fact[n]);
	for(int i=n-1;i>=0;i--) invfact[i]=invfact[i+1]*(i+1)%mod;
	for(int i=1;i<=n;i++) invx[i]=fact[i-1]*invfact[i];
}
int main(){
	scanf("%d %d",&n,&k);
	ini(n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j+=i) d[j].push_back(i);
	}
	int mina=0;//贪心得出的最小次数 
	for(int i=n;i>=1;i--){
		if(a[i]){
			for(int j=0;j<(int)d[i].size();j++) a[d[i][j]]^=1;
			mina++;
		}
	}
	dp[n]=1;
	for(int i=n-1;i>=1;i--){
		dp[i]=(1+(n-i)*(dp[i+1]+1)%mod*invx[i]%mod)%mod; 
	}
	if(mina<=k) printf("%lld\n",mina*fact[n]%mod);//初始局面的答案就<=k,直接操作
	else{
		ll ans=0;
		for(int i=mina;i>=k+1;i--) ans=(ans+dp[i])%mod;//从mina到mina-1,mina-2....k
		ans=(ans+k)%mod;
		printf("%lld\n",ans*fact[n]%mod); 
	} 
	
}
posted @ 2020-05-24 21:36  birchtree  阅读(144)  评论(0编辑  收藏  举报