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;
}
View Code

 

$By\ LIN452$

$2017.06.08$

posted @ 2017-06-07 13:50  LIN452  阅读(12)  评论(0编辑  收藏  举报