【SHOI2017】【期望dp】分手是祝愿

9 篇文章 0 订阅
8 篇文章 0 订阅

【描述】
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 取模之后的结果。

【输入】
第一行两个整数 n; k。

接下来一行 n 个整数,每个整数是 0 或者 1,其中第 i 个整数表示第 i 个灯的初始情况。

【输出】
输出一行,为操作次数的期望乘以 n 的阶乘对 100003 取模之后的结果。

• 对于 0% 的测试点,和样例一模一样;

• 对于另外 30% 的测试点, n ≤ 10;

• 对于另外 20% 的测试点, n ≤ 100;

• 对于另外 30% 的测试点, n ≤ 1000;

• 对于 100% 的测试点, 1 ≤ n ≤ 100000; 0 ≤ k ≤ n;

• 对于以上每部分测试点,均有一半的数据满足 k = n。

【思路】

除了预处理约数看起来无从下手。不过不管写不写代码,我们已经拿到了第一档部分分。
我们先来考虑考虑最优策略是怎么样的。简单分析得出下列性质:
1.按键是独特的,不能通过一些按键的组合达到和一个按键等价的效果。
2.对于灯的若干次操作等价于两种操作:操作一次,不操作。
这是两个很有趣的性质,这意味着:当每个灯都关上时,每个灯的等价意义下的操作是确定的。那么考虑贪心,从大到小依次考虑,如果灯亮则按灯,这一定是最优策略。我们这样就可以统计出我们需要对多少盏灯进行操作。由于第一条性质,我们知道在任何策略中,等价意义下这些灯是必须操作的且只能操作这些灯。我们考虑状态 f [ i ] f[i] f[i]表示还剩i盏灯需要操作的状态下,转移到还剩i-1盏灯需要操作的期望操作次数。我们有 i / n i/n i/n的概率进行正确的操作,此时操作次数为1,我们有 ( n − i ) / n (n-i)/n (ni)/n的概率错误操作,此时,根据性质1,我们必须在以后把这次错误操作修正回来,所以操作次数为f[i+1]+f[i]+1。移项化简得到f[i]的递推式即可进行递推。设need表示我们需要操作的灯的数量,答案显然为 ∑ i = k + 1 n e e d f [ i ] + k \sum_{i=k+1}^{need}f[i]+k i=k+1needf[i]+k
代码:

#include<bits/stdc++.h>
#define re register
using namespace std;
const int N=1e5+5;
const int mod=1e5+3;
inline int red(){
    int data=0;bool w=0; char ch=0;
    ch=getchar();
    while(ch!='-' && (ch<'0' || ch>'9')) ch=getchar();
    if(ch=='-') w=1,ch=getchar();
    while(ch>='0' && ch<='9') data=(data<<3)+(data<<1)+ch-'0',ch=getchar();
    return w?-data:data;
}
int n,m,b,c,num=0;
bool a[N];
vector<int>g[N];
double f[N];
int fac[N]={1,1};
int inv[N]={0,1};
int ans=0;
inline int mul(const int&a,const int &b){return 1ll*a*b%mod;}
void pre(){
	for(int re i=2;i<=n;i++){
		fac[i]=mul(fac[i-1],i);
		inv[i]=mul(inv[mod%i],mod-mod/i);
	}
}
int main()
{
	n=red();m=red();pre();
	for(int re i=1;i<=n;i++)a[i]=red();
	for(int re i=1;i<=n;i++)
		for(int re j=i;j<=n;j+=i)
			g[j].push_back(i);
	for(int re i=n;i;--i){
		if(a[i]){++num;
			for(int re j=g[i].size()-1;~j;--j)
				a[g[i][j]]^=1;
		}
	}
	if(num<m)return cout<<mul(num,fac[n])<<"\n",0;
	f[n]=1;
	for(int re i=n-1;i;--i)f[i]=(1+mul(mul(n-i,f[i+1]+1),inv[i]))%mod;
	for(int re i=num;i>m;--i)(ans+=f[i])%=mod;
	cout<<mul(ans+m,fac[n])<<"\n";
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值