zoukankan      html  css  js  c++  java
  • [六省联考2017]分手是祝愿

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

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

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

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

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

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

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

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

    \[f_i=\dfrac{i}{n}\times f_{i-1}+\dfrac{n-i}{n}\times f_{i+1}+1 \]

    特别地,我们令对于所有\(i\leq k\),有\(f_i=i\)

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

    当然,这种方法有一个坏处,就是写起来太麻烦了,因为你开不下\(n^2\)的数组,只能记录对角线周围几行,这就导致操作极其恶心,特别是当主对角线上元素为\(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;
    } 
    

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

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

    则有

    \[f_i=\dfrac{i}{n}\times 1+\dfrac{n-i}{n}\times(f_{i}+f_{i+1}+1) \]

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

    将其化简,最终得到

    \[f_i=\dfrac{f_{i+1}(n-i)+n}{i} \]

    故直接递推即可。最终结果只要对\(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;
    } 
    

  • 相关阅读:
    vue--组件基础
    vue中的一些知识点--多看文档
    关于组件--React
    数组方法-->map()
    正则表达式使用
    border-image 和 border-color 不能同时使用问题
    gulp
    oninput 中文输入
    linux文档权限
    为什么使用 use strict
  • 原文地址:https://www.cnblogs.com/Troverld/p/14610853.html
Copyright © 2011-2022 走看看