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

一、题目

点此看题

二、解法

(50) 分的做法可以一眼看出来,就是我们从后往前关灯,可以证明这样步数一定是最小的,但其实可以得 (75)

其实上面的方法不止是最小的,而且是唯一的。意思是如果只看成单个灯的开关的话,一定是某个灯被按奇数次,某个灯被按偶数次,这是唯一确定的。知道了这个就可以抛弃掉那个很操蛋的修改约数了。

剩下的就是期望 (dp) 乱搞了,传统方法是定义 (f[i]) 还剩 (i) 个灯要关的期望步数,本题不能直接高斯消元,但是可以让 (f[i]=f[i-1]+b[i]) 来推 (b[i])(这种方法就不展开讲了)

还有更好的方法,把开关灯的过程定义在状态里面(其实也有先例),设 (f[i]) 表示从 (i) 个灯关到 (i-1) 个灯的期望步数,那么转移就很好写了:

[f[i]=1+frac{n-i}{n} imes (f[i]+f[i+1]) ]

也就是有 (frac{n-i}{n}) 的概率按错就需要倒回去重新按,我们把这个柿子移下项:

[f[i]=frac{n+(n-i) imes f[i+1]}{i} ]

那么就可以直接 (O(n)) 递推了,算答案的时候求出最小按的次数 (cnt),如果这个次数比 (k) 小那么直接就是答案,否则从 (f[cnt]+f[cnt-1]...+f[k+1]) 求出到使用最小方案的期望步数,乘上 (n) 的阶乘即可。

#include <cstdio>
#define int long long
const int M = 100005;
const int MOD = 100003;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,k,cnt,ans,a[M],f[M];
int qkpow(int a,int b)
{
	int r=1;
	while(b>0)
	{
		if(b&1) r=r*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return r;
}
signed main()
{
	n=read();k=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	for(int i=n;i>=1;i--)
	{
		f[i]=(n+(n-i)*f[i+1]%MOD)*qkpow(i,MOD-2)%MOD;
		if(a[i])
		{
			cnt++;
			for(int j=1;j*j<=i;j++)
				if(i%j==0)
				{
					a[j]^=1;
					if(j*j!=i) a[i/j]^=1;
				}
		}
	}
	if(cnt<=k) ans=cnt;
	else
	{
		for(int i=cnt;i>k;i--) ans=(ans+f[i])%MOD;
		ans=(ans+k)%MOD;
	}
	for(int i=1;i<=n;i++) ans=ans*i%MOD;
	printf("%lld
",ans);
}
原文地址:https://www.cnblogs.com/C202044zxy/p/14598403.html