【BZOJ4872】【SHOI2017】分手是祝愿 期望DP

题目大意

  有\(n\)盏灯和\(n\)个开关,初始时有的灯是亮的,有的灯是暗的。按下第\(i\)个开关会使第\(j\)盏灯的状态被改变,其中\(j|i\)。每次你会随机操作一个开关,直到可以通过不多于\(k\)次操作使所有灯都灭掉,然后按照操作次数最小的方案操作。求期望的操作次数\(\times n!~mod~100003\)

  \(1\leq n\leq 100000,0\leq k\leq n\)

题解

  首先不能通过操作任意个不同的开关使得灯的状态不变,因为最大那个开关对应的灯的状态一定会改变。

  所以我们每次操作亮着的灯中编号最大的那盏对应的开关,直到所有灯都灭掉。这个操作步骤一定是最优步骤。记下操作次数\(num\)

  设\(f_i\)\(i\)盏灯变成\(i-1\)盏灯期望操作次数,有:

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

  特殊的,\(f_{n+1}=0\)

  最后把答案乘上\(n!\)

  时间复杂度:\(O(n\sqrt n)\)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<ctime>
#include<utility>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
ll p=100003;
ll fp(ll a,ll b)
{
	ll s=1;
	while(b)
	{
		if(b&1)
			s=s*a%p;
		a=a*a%p;
		b>>=1;
	}
	return s;
}
int a[100010];
ll f[100010];
int main()
{
	int n,k;
	scanf("%d%d",&n,&k);
	int i,j;
	for(i=1;i<=n;i++)
		scanf("%d",&a[i]);
	int num=0;
	for(i=n;i>=1;i--)
		if(a[i])
		{
			num++;
			for(j=1;j*j<=i;j++)
				if(i%j==0)
				{
					a[j]^=1;
					if(j*j!=i)
						a[i/j]^=1;
				}
		}
	ll s=1;
	f[n+1]=1;
	for(i=n;i>=1;i--)
		f[i]=(n+(n-i)*f[i+1]%p)%p*fp(i,p-2)%p;
	if(num<=k)
		s=num;
	else
	{
		s=0;
		for(i=num;i>k;i--)
			s=(s+f[i])%p;
		s=(s+k)%p;
	}
	for(i=1;i<=n;i++)
		s=s*i%p;
	printf("%lld\n",s);
	return 0;
}
posted @ 2018-03-05 21:07  ywwyww  阅读(233)  评论(0编辑  收藏  举报