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

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

首先,本题的基础是想到一种求解的方式:

当前第n盏灯只能被第n个开关控制,故我们只能操纵第n个开关将其搞灭。当其熄灭后,又相当于进入了n1的游戏——

因此,我们可以发现(或者瞎猜出来),任意局面都有唯一的最优方法,它操作在一组特定位置上。假如一次操作作用在应该作用的位置上,则总还需要的操作次数减一;否则,加一。

(详细的证明是因为我们如果把每个开关看作一个n维向量,则n个开关所对应的向量是线性无关的;换句话说,任意一个开关都是不可替代的)

于是我们可以先通过上面的方法,在O(nn)或者O(nlnn)的时间内——这取决于你使用的算法——求出初始状态需要多少次操作,设为P

则如果Pk,直接按照P次操作输出即可;否则,我们考虑它期望多少次操作才能够只剩k次操作。

一组naive的想法是设fi表示当剩下i次操作时,消减到k的期望操作次数。则我们有

fi=in×fi1+nin×fi+1+1

特别地,我们令对于所有ik,有fi=i

则我们就可以暴力高斯消元得出答案。详情可见某道经典老题

当然,这种方法有一个坏处,就是写起来太麻烦了,因为你开不下n2的数组,只能记录对角线周围几行,这就导致操作极其恶心,特别是当主对角线上元素为0,需要手动交换相邻两行时。

代码:

#include<bits/stdc++.h>
using namespace std;
const int mod=100003;
int n,m,P;
bool a[100100];
int g[100100][7],f[100100];
int ksm(int x,int y){
	int z=1;
	for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)z=1ll*x*z%mod;
	return z;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=n;i;i--)if(a[i]){
		P++;
		for(int j=1;j*j<=i;j++){
			if(i%j)continue;
			a[j]^=1;
			if(j*j<i)a[i/j]^=1;
		}
	}
	if(P<=m){for(int i=1;i<=n;i++)P=1ll*P*i%mod;printf("%d\n",P);return 0;}
	int invn=ksm(n,mod-2);
	for(int i=1;i<=n;i++){
		g[i][6]=i;
		if(i<=m){g[i][2]=1,g[i][5]=i;continue;}
		int p=1ll*i*invn%mod,q=(mod+1-p)%mod;
		g[i][1]=p;
		g[i][2]=mod-1;
		g[i][3]=q;
		g[i][5]=mod-1;
	}
	for(int i=1;i<n;i++){
		if(!g[i][2])swap(g[i][2],g[i+1][1]),swap(g[i][3],g[i+1][2]),swap(g[i][4],g[i+1][3]),swap(g[i][5],g[i+1][5]),swap(g[i][6],g[i+1][6]);
		int delta=1ll*g[i+1][1]*ksm(g[i][2],mod-2)%mod;
		(g[i+1][1]+=mod-1ll*delta*g[i][2]%mod)%=mod;
		(g[i+1][2]+=mod-1ll*delta*g[i][3]%mod)%=mod;
		(g[i+1][3]+=mod-1ll*delta*g[i][4]%mod)%=mod;
		(g[i+1][5]+=mod-1ll*delta*g[i][5]%mod)%=mod;
	}
	f[n]=1ll*g[n][5]*ksm(g[n][2],mod-2)%mod;
	for(int i=n-1;i;i--){
		(g[i][5]+=mod-1ll*g[i][4]*f[i+2]%mod)%=mod;
		(g[i][5]+=mod-1ll*g[i][3]*f[i+1]%mod)%=mod;
		f[i]=1ll*g[i][5]*ksm(g[i][2],mod-2)%mod;
	}
	int res=f[P];
	for(int i=1;i<=n;i++)res=1ll*res*i%mod;printf("%d\n",res);
	return 0;
} 

还有一种做法就比较小清新了。

我们设fi表示当前还剩i次操作时,削减掉一次操作的期望时间。

则有

fi=in×1+nin×(fi+fi+1+1)

理解:前一半是抽到一个需要的位置的概率;后一半是抽到一个不需要的位置时,你需要将次数从i+1先消减到i再消减到i1

将其化简,最终得到

fi=fi+1(ni)+ni

故直接递推即可。最终结果只要对f求和即可。(别忘记最后还得加上一个k!)

代码:

#include<bits/stdc++.h>
using namespace std;
const int mod=100003;
int n,m,P;
bool a[100100];
int f[100100];
int ksm(int x,int y){
	int z=1;
	for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)z=1ll*x*z%mod;
	return z;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=n;i;i--)if(a[i]){
		P++;
		for(int j=1;j*j<=i;j++){
			if(i%j)continue;
			a[j]^=1;
			if(j*j<i)a[i/j]^=1;
		}
	}
	if(P<=m){for(int i=1;i<=n;i++)P=1ll*P*i%mod;printf("%d\n",P);return 0;}
	for(int i=n;i>=0;i--)f[i]=1ll*(1ll*(n-i)*f[i+1]%mod+n)%mod*ksm(i,mod-2)%mod;
	for(int i=n;i>=0;i--)(f[i]+=f[i+1])%=mod;
	int res=(f[m+1]-f[P+1]+m+mod)%mod;
	for(int i=1;i<=n;i++)res=1ll*res*i%mod;printf("%d\n",res);
	return 0;
} 

posted @   Troverld  阅读(72)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示