NOI2015 寿司晚宴
题意:
给定$n$,对于$[2,n]$中的每个正整数,从中选出两个集合,使得两个集合各自的LCM互质,答案对$p$取模.
$n<=500,p<=1^{9}$
题解:
暴力做法,预处理出$<=n$的所有素因子.
进行状压DP,$dp[i][a][b]$表示前$i$个点,所选集合中的素因子集合分别为$a$,$b$的方案数.
可以通过$30%$的数据.
我们需要优化该算法.
原算法中考虑了每一个素因子,考虑能否减少素因子的个数来优化算法呢?
首先,大于$n/2$的素因子是不用考虑的,因为最多只存在一个数字含有该素因子.
但是$<=n/2$的素因子还是很多,这样起不到实质性的优化,如果考虑$\sqrt{n}$内的素因子的呢?
这样每个数字最多只会含有一个>$\sqrt{n}$的因子,假设为$k$.
按照原来的思路DP,$dp[i][a][b]$表示前$i$个点,所选集合中的素因子集合分别为$a$,$b$的方案数.
但是我们还要考虑$k$对答案的影响.
只要我们把$k$相同的元素放在一起考虑.
那么就有三种选择,选出一个集合全部在$a$,全部在$b$,都不选.
那么只要分别对放在$a$,放在$b$进行DP即可.
这样就可以把素因子的个数减少到8个,而DP的复杂度却没有升高.
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; const int M=505,S=8; int n,P,mark[M],pri[M],m=0; int f[M][1<<S][1<<S],t[1<<S][1<<S];//3qw struct node{ int v,p; bool operator<(const node &tmp)const{ return p<tmp.p; } bool operator==(const node &tmp)const{ return p==tmp.p&&(p!=1||v==tmp.v); } }w[M]; void init(){ int mx=sqrt(n),i,j,k; for(i=2;i<=n;i++)w[i].p=i; for(i=2;i<=mx;i++){ if(mark[i])continue; pri[m]=i; for(j=i;j<=n;j+=i){ w[j].v|=(1<<m); while(w[j].p%i==0)w[j].p/=i; mark[j]=1; } m++; } sort(w+2,w+1+n);//从第二个数字开始 } void Add(int &x,int y){x+=y;if(x>=P)x-=P;if(x<0)x+=P;} int main(){ scanf("%d %d",&n,&P); init(); int i,j,k,en,ful=(1<<m)-1,a,b,v; f[1][0][0]=t[0][0]=1; for(i=2;i<=n;i=en){ for(j=0;j<=ful;j++){ for(k=0;k<=ful;k++){ t[j][k]=f[i-1][j][k]; } } for(j=i;j<=n&&w[j]==w[i];j++); en=j; for(j=i;j<en;j++){ v=w[j].v; for(a=ful;a>=0;a--){ for(b=ful;b>=0;b--){ if(a&b)continue; if(b&v)continue; Add(t[a|v][b],t[a][b]); } } } for(a=0;a<=ful;a++){ for(b=0;b<=ful;b++){ Add(f[en-1][a][b],t[a][b]); Add(f[en-1][a][b],-f[i-1][a][b]); t[a][b]=f[i-1][a][b]; } } for(j=i;j<en;j++){ v=w[j].v; for(a=ful;a>=0;a--){ for(b=ful;b>=0;b--){ if(a&b)continue; if(a&v)continue; Add(t[a][b|v],t[a][b]); } } } for(a=0;a<=ful;a++){ for(b=0;b<=ful;b++){ Add(f[en-1][a][b],t[a][b]); } } } int ans=0; for(i=0;i<=ful;i++){ for(j=0;j<=ful;j++){ if(!(i&j))Add(ans,f[n][i][j]); } } printf("%d\n",ans); return 0; }
$By\ LIN452$
$2017.06.08$