[NOI Online #3 提高组]优秀子序列,子集Dp

正题

      Portal

      讲讲比赛的心路历程。

      看了第一题,直接过,看了第二题,以为是棵树,讨论一下奇偶就可以了,过,看到第三题,发现就是一个3^18的子集Dp。

      诶,今天怎么这么简单,码码码,码完T1的对拍,拍上了,看T2,发现是张图,n只有200,ai这么大,只能是矩阵,码码码,码了一个n^4logn,貌似过不去,预处理幂矩阵,减小常数,依然很慢,发现只从点1开始的话可以直接n^2,那么就做完了,码码码,拍上了。

       T3很快也拍上了,最大数据测了一下1.5s,有点慌(flag

       11点就坐着无聊了,也没又想到什么更奇妙的方法,算了算大概也就两亿左右,不会跑满3^18。

       第二天看题解发现三个做法都对了,到今天发现第3题只有70,看了看OYJason,嘻嘻应该跟我一样被卡了,在洛谷上找题解,发现标算真的和我不一样,具体来说:

       我的做法是对于每一个值来说,枚举可以被它递推的Dp位置,这样做的时间复杂度几乎是满的,也就是\sum_{i=1}^{200000} 2^{18-l(i)},这个东西用二项式定理算一下就发现和<=3^{18}

for(int i=1;i<=mmax;i++)if(t[i]){
		T=M^i;for(int j=T;j;j=(j-1)&T)if(f[j]) (f[j|i]+=f[j]*t[i]%mod)%=mod;
		f[i]+=t[i],f[i]>=mod?f[i]-=mod:0;
	}

       但是更快的方式是,枚举Dp位置,考虑一种可行的子序列,将它从小到大排序,第i个位置的值一定>前i-1个位置的值的和。

       证明考虑二进制的性质,那么在枚举的时候,可行的转移值一定大于Dp位置,别小看这句话,足足可以让您剩下1.5s。(我吐了

for(int i=0;i<=M;i++) if(f[i]){
		T=M^i;for(int j=T;j>i;j=(j-1)&T)if(t[j]) (f[i|j]+=f[i]*t[j])%=mod;
	}

       hyy帮我卡了一晚上也没“不使用O2”通过。开了O2也是卡着1.5s过去的。

       总结教训就是:递推的时候要多考虑递推方式,主要就是有上面两种,也许会带来意想不到的惊喜?

#include<bits/stdc++.h>
using namespace std;

const int N=300010;
int p[N],phi[N],t[N],n;
const int mod=1000000007;
long long f[N];
bool vis[N];

inline void read(int&x){
	static char ch;ch=getchar();x=0;
	while(ch<'0' || ch>'9') ch=getchar();
	while(ch>='0' && ch<='9') x=x*10+ch-'0',ch=getchar();
} 

int main(){
	phi[1]=1;
	for(int i=2;i<=300000;i++){
		if(!vis[i]) p[++p[0]]=i,phi[i]=i-1;
		for(int j=1;j<=p[0] && i*p[j]<=300000;j++){
			vis[i*p[j]]=true;
			if(i%p[j]==0) {phi[i*p[j]]=phi[i]*p[j];break;}
			phi[i*p[j]]=phi[i]*phi[p[j]];
		}
	}
	read(n);
	int x,mmax=0;f[0]=1;
	while(n--) read(x),t[x]++,mmax=max(mmax,x);
	int M=1,T,op;
	while(M<=mmax) M<<=1;M-=1;
	for(int i=0;i<=M;i++) if(f[i]){
		T=M^i;for(int j=T;j>i;j=(j-1)&T)if(t[j]) (f[i|j]+=f[i]*t[j])%=mod;
	}/*
	for(int i=1;i<=mmax;i++)if(t[i]){
		T=M^i;for(int j=T;j;j=(j-1)&T)if(f[j]) (f[j|i]+=f[j]*t[i]%mod)%=mod;
		f[i]+=t[i],f[i]>=mod?f[i]-=mod:0;
	}*/
	long long ans=0;
	for(int i=0;i<=M;i++) if(f[i]) ans+=phi[i+1]*f[i]%mod;ans%=mod;
	for(int i=1;i<=t[0];i++) ans=ans*2%mod;
	printf("%lld\n",ans);
} 

猜你喜欢

转载自blog.csdn.net/Deep_Kevin/article/details/106389627