• 【考试总结】20220603


    石子游戏

    将问题转化为求全集的最小的子集使得其异或和为全部元素异或和

    \(H\) 为所有元素构成的集合幂级数,\(F_i\) 表示用 \(i\) 个互不相同的元素异或得到的结果的集合幂级数,不难找到转移式 \(F_i=F_{i-1}H-F_{i-2}(n-i+2)(i-1)\)

    暴力的想法就是计算出来 \(F_i\) 的点值后每次做逆变换判定是不是目标位置非零

    注意到要张成 所有元素张成的空间 最少需要线性基秩大小个元素,也就是 \(\Theta(\log)\) 个,那么每次根据定义式求逆变换后目标位置系数复杂度就是对的

    我写了四模数来防止非零

    Code Display
    const int N=530000;
    int n;
    struct data{
    	int a,b,c;
    	ull d;
    	data(){}
    	data(int aa,int bb,int cc,ull dd){a=aa; b=bb; c=cc; d=dd;}
    	data operator +(const data &x)const{
    		return data(add(a,x.a,mod1),add(b,x.b,mod2),add(c,x.c,mod3),d+x.d);
    	}
    	data operator -(const data &x)const{
    		return data(del(a,x.a,mod1),del(b,x.b,mod2),del(c,x.c,mod3),d-x.d);
    	}
    	data operator *(const int &x)const{
    		return data(mul(a,x,mod1),mul(b,x,mod2),mul(c,x,mod3),d*x);
    	}
    	data operator *(const data &x)const{
    		return data(mul(a,x.a,mod1),mul(b,x.b,mod2),mul(c,x.c,mod3),d*x.d);
    	}
    }f[3][N],H[N];
    inline void FWT(data *f,int lim){
    	for(int p=2;p<=lim;p<<=1){
    		int len=p>>1;
    		for(int k=0;k<lim;k+=p) for(int l=k;l<k+len;++l){
    			data x=f[l],y=f[l+len];
    			f[l]=x+y;
    			f[l+len]=x-y;
    		}
    	}
    }
    signed main(){
    	n=read();
    	int tar=0,Mx=0,U=1;
    	rep(i,1,n){
    		int x=read();
    		ckmax(Mx,x);
    		f[1][x].a++;
    		f[1][x].b++;
    		f[1][x].c++;
    		f[1][x].d++;
    		tar^=x;
    	}
    	if(!tar) print(n),exit(0);
    	if(f[1][tar].a) print(n-1),exit(0);
    	while(U<=Mx) U<<=1; --U;
    	f[0][0]=data(1,1,1,1);
    	FWT(f[0],U+1);
    	FWT(f[1],U+1);
    	rep(i,0,U) H[i]=f[1][i];
    	int num=2;
    	while(1){
    		int p1=num%3,p2=(num-1)%3,p3=(num-2)%3;
    		for(int i=0;i<=U;++i){
    			int v=(num-1)*(n-num+2);
    			data tmp={v%mod1,v%mod2,v%mod3,v};
    			f[p1][i]=f[p2][i]*H[i]-f[p3][i]*tmp;
    		}
    		data res=data(0,0,0,0);
    		for(int i=0;i<=U;++i){
    			int pc=__builtin_popcount(i&tar)&1;
    			if(pc) res=res-f[p1][i];
    			else res=res+f[p1][i];
    		}
    		if(res.a||res.b||res.c||res.d) print(n-num),exit(0);
    		++num;
    	}
    	return 0;
    }
    

    函数

    构造 \(H * G=F\) ,其中 \(*\) 表示狄利克雷卷积

    \(G(x)=x^k\) 是积性函数,那么 \(H\) 也是积性函数

    那么手玩发现 \(H(1)=G(1)=1\) ,对于质数 \(p\)\(F(p)=H(p)G(1)+G(p)H(1)\Rightarrow G(p)=0\)

    根据 \(H\) 是积性函数那么如果某个 \(n\) 的唯一分解中有质数指数为 \(1\) 那么 \(H(n)=0\) ,所以非零的 \(n\) 只有 \(\sqrt N\)

    尝试考察 \(e>1\)\(H(p^e)\) 的数值,使用数学归纳法配合等比数列求和可以得到 \(H(p^e)=p^k-p^{2k}\)

    那么 \(\displaystyle\sum\limits_{i=1}^n F(i)=\sum_{ij\le n}G(i)H(j)=\sum_{i=1}^n H(n)\sum_{j=1}^{\left\lfloor\frac{n}i\right\rfloor} G(j)\)

    对于所有非零的 \(H(i)\) 后半部分自然数幂前缀和算即可

    听说这就是 powerful number 筛

    Code Display
    const int N=8e6+10;
    namespace calculator{
    	int K;
    	int x[50],pre[50],suf[50],y[50],ifac[50];
    	inline int Lagrange(int X){
    		if(X<=K) return y[X];
    		rep(i,1,K) pre[i]=mul(pre[i-1],del(X%mod,i));
    		Down(i,K,1) suf[i]=mul(suf[i+1],del(X%mod,i));
    		int res=0;
    		rep(i,1,K){
    			int val=mul(y[i],mul(pre[i-1],suf[i+1]));
    			ckmul(val,mul(ifac[i-1],ifac[K-i]));
    			if((K-i)&1) val=del(0,val);
    			ckadd(res,val);
    		}
    		return res;
    	}	
    	inline void init(int k){
    		K=k+3;
    		pre[0]=suf[K+1]=1;
    		rep(i,1,K) x[i]=i,y[i]=add(y[i-1],ksm(x[i],k));
    		int fac=1; ifac[0]=1;
    		for(int i=1;i<=K;++i){
    			ckmul(fac,i);
    			ifac[i]=ksm(fac,mod-2);
    		}
    		return ;
    	}
    }
    int n,k,block;
    int pri[N],cnt,id1[N],id2[N];
    bool fl[N];
    inline int get_id(int x){return x>block?id2[n/x]:id1[x];}
    int pw[N],coef[N],val[N],tot;
    inline void dfs(int n,int id,int H){
    	ckadd(coef[get_id(n)],H);
    	for(int i=id+1;i<=cnt&&pri[i]*pri[i]<=n;++i){	
    		int y=pri[i];
    		int curf=mul(pw[i],del(1,pw[i]));
    		do{
    			y*=pri[i];
    			dfs(n/y,i,mul(H,curf));
    		}while(y<=n/pri[i]);
    	}	
    	return ;
    }
    signed main(){
        n=read(); k=read(); block=sqrt(n)+10;
    	calculator::init(k);
    	for(int i=2;i<=block;++i){
            if(!fl[i]){
                pri[++cnt]=i;
    			pw[cnt]=ksm(i,k);
            }
            for(int j=1;j<=cnt&&pri[j]*i<=block;++j){
                fl[i*pri[j]]=1;
                if(i%pri[j]==0) break;
    		}
        }
        for(int l=1,r;l<=n;l=r+1){
            r=n/(val[++tot]=n/l);
            if(val[tot]<=block) id1[val[tot]]=tot;
            else id2[r]=tot;
        }
    	dfs(n,0,1);
    	int ans=0;
        for(int i=1;i<=tot;++i) if(coef[i]){
    		ckadd(ans,mul(coef[i],calculator::Lagrange(val[i])));
    	}
    	print(ans);
    	return 0;
    }
    

    先考察 \(m=0\) 的部分,如果某个元素在某位小于限制那么可以让其它元素后续任选,使用它调配异或值

    对于有多个元素在当前位小于限制的情况可以设计一个 \(\rm DP:f_{i,0/1,0/1}\) 表示这位当前选择的 \(1\) 的奇偶性,以及这位是不是已经有元素小于限制

    最开始的小于限制的元素数值是一定的,不能附乘 \(2^{bit}\)

    如果所有元素都贴上界那么可以到删掉这位继续考虑最高位,注意如果 \(\oplus \lim_i=C\) 要额外加一

    考虑使用集合容斥来处理在最终方案中某一权值全部元素的分组情况,也就是钦定一些元素权值相同,不同的钦定之间不要求不同,要求这样的钦定满足边的限制

    \(c_S\) 表示将 \(S\) 中元素钦定成相同权值的容斥系数,仍然使用 \(\rm ABC236 Ex\) 的计算方式,全部连边方式系数之和取决于点集中是否有边,否则枚举 \(1\) 所在联通块,判定外部是否没有任何边即可

    \(f_{S,T}\) 表示已经钦定了 \(S\) 中的点,奇数大小集合的最小 \(\lim\) 取值点为 \(T\) 的方案数,最终答案的统计是 \(f(U,S)\) 乘对 \(S\)\(\lim\) 做上面 \(\rm DP\) 得到的方案数

    此处直接枚举添加 \(S\) 补集的子集并根据实际含义转移即可,也就是说再添加偶数大小钦定时需要附上 \(\min \lim+1\) 的系数

    如果强制添加的 补集的子集 包含补集的最小值那么可以证明复杂度是 \(\Theta(3^n)\) ,另一种强制包含补集 \(\rm lowbit\) 的做法也过了,不知道能不能卡

    Code Display
    int lim[20],n,m,C;
    bool legal[1<<15];
    int sum[1<<15],coef[1<<15],argmin[1<<15],pw[20];
    int f[60000010];
    int a[20],dp[20][2][2];
    inline int DP(int S){
    	int ans=0;
    	m=0;
    	rep(i,1,n) if(S>>(i-1)&1) a[++m]=lim[i];
    	for(int i=60;i>=1;--i){	
    		int cnt=0;	
    		memset(dp,0,sizeof(dp));
    		dp[0][0][0]=1;
    		for(int j=1;j<=m;++j){
    			int rem=a[j]&((1ll<<(i-1))-1);
    			rem=(rem+1)%mod;
    			for(int x=0;x<2;++x) for(int y=0;y<2;++y) if(dp[j-1][x][y]){
    				if(a[j]>>(i-1)&1){
    					if(x) ckadd(dp[j][1][y],mul(dp[j-1][x][y],(1ll<<(i-1))%mod));
    					else ckadd(dp[j][1][y],dp[j-1][x][y]);
    					ckadd(dp[j][x][y^1],mul(dp[j-1][x][y],rem));
    				}else{
    					ckadd(dp[j][x][y],mul(dp[j-1][x][y],rem));
    				}
    			}
    			if(a[j]>>(i-1)&1) ++cnt;
    		}
    		ckadd(ans,dp[m][1][C>>(i-1)&1]);
    		if((cnt&1)!=(C>>(i-1)&1)) break;
    		if(i==1) ckadd(ans,1);
    	}
    	return ans;
    }
    signed main(){
    	n=read(); m=read(); C=read();
    	rep(i,1,n) lim[i]=read();
    	int U=(1<<n)-1;
    	for(int i=0;i<=U;++i) legal[i]=1;
    	while(m--){
    		int u=read(),v=read();
    		for(int i=0;i<=U;++i) if((i>>(u-1)&1)&&(i>>(v-1)&1)) legal[i]=0;
    	}
    	pw[0]=1;
    	rep(i,1,n) pw[i]=pw[i-1]*3;
    	for(int i=0;i<=U;++i){
    		argmin[i]=-1;
    		for(int j=1;j<=n;++j) if(i>>(j-1)&1){
    			sum[i]+=pw[j];
    			if(argmin[i]==-1||lim[j]<lim[argmin[i]]) argmin[i]=j;
    		}
    	}
    	for(int i=1;i<=U;++i){
    		int lb=i&(-i);
    		coef[i]=legal[i];
    		for(int j=(i-1)&i;j;j=(j-1)&i){
    			if((j&lb)&&legal[i^j]) ckdel(coef[i],coef[j]);
    		}
    	}
    	f[0]=1;
    	for(int S=0;S<=U;++S){
    		for(int T=S;;T=(T-1)&S){
    			if(f[sum[S]+sum[T]]){
    				int oth=U^S;
    				for(int x=oth;x;x=(x-1)&oth){
    					if((x&(-x))!=(oth&(-oth))) continue;
    					if(__builtin_parity(x)){
    						ckadd(f[sum[S|x]+sum[T|(1<<(argmin[x]-1))]],mul(f[sum[S]+sum[T]],coef[x]));
    					}else{
    						ckadd(f[sum[S|x]+sum[T]],mul(f[sum[S]+sum[T]],mul(coef[x],(lim[argmin[x]]+1)%mod)));
    					}
    				}
    			}
    			if(!T) break;
    		}
    	}
    	int ans=0;
    	for(int i=0;i<=U;++i) if(f[sum[U]+sum[i]]) ckadd(ans,mul(f[sum[U]+sum[i]],DP(i)));
    	print(ans);
    	return 0;
    }
    

  • 相关阅读:
    资源
    p/invoke碎片,对结构体的处理
    p/invoke碎片--对数组的封送处理
    p/invoke碎片--对类的封送处理
    CSS--background
    在Main方法中设置异常的最后一次捕捉
    记一次WinForm中屏蔽空格键对按钮的作用
    记一次WinForm程序中主进程打开子进程并传递参数的操作过程(进程间传递参数)
    知识点
    安卓N特性
  • 原文地址:https://www.cnblogs.com/yspm/p/16340476.html
Copyright © 2020-2023  润新知