[六省联考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}\times (f[i]+f[i+1]) \]

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

\[f[i]=\frac{n+(n-i)\times 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\n",ans);
}
posted @ 2021-03-30 19:29  C202044zxy  阅读(61)  评论(0编辑  收藏  举报