NOIP模拟:乘积(状压DP)

题意:
选择不超过 K N 以内的正整数撑起来,使得乘积是一个无平方因子数(不能够被任意一个质数的平方整除),一共有多少种取法?
(n500,K500)

题解:
首先,很容易想到状压,压缩当前已经选过某质数的状态。对于质数个数小于 20 可以直接过,但是问题是现在 n 以内的质数个数很多,无法压缩。

考虑状压DP的本质:从前往后依次加入数,并更新之前的所有没有与他共用质因数的状态。显然,对于那些质因数分解后只有小质数的数可以直接状压DP,现在考虑如何加入其他大的数。

设置一个分界点 n 来区分大小质因数,一个数只可能包含至多一个大于 n 的质因数,所以每个质数都会而且只会被枚举一次,现在从小( n )到大枚举这些质因数:

现在已经枚举到质因数 j ,开始枚举包含 j 的每个数 aj ,还是将 aj 质因数分解,设置一个 temp 数组(代码中可以不设置,这里为了方便),因为前面的所有状态都没有包含 j ,而且状压DP保证了不会重复小质数,可以通过同样的状压DP方式从之前的DP状态转移过来。
处理完所有 aj 后把 temp 数组加入 dp 数组继续处理 j2 即可。
其实这就是个分组背包而已

代码中先处理了所有不包含 1 的情况,最后对于小于 k 的情况乘 2 (选 1 和不选 1 ),对于等于 k 的情况直接加(不能选 1 ),最后再加上 1 即可(只选 1 ).

#include
using namespace std;
inline int read(){
    char ch=getchar();int i=0,f=1;
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=getchar();}
    return i*f;
}
const int LIM=(1<<8)+20,Maxn=5e2+50;
const int prime[8]={2,3,5,7,11,13,17,19};
const int Mod=1e9+7;
int T,n,k,dp[Maxn][LIM],status[Maxn],lim=(1<<8)-1;
vector<int>v[Maxn];
inline void pre(){
    for(register int i=500;i>=2;i--){
        int tmp=i,bz=1;
        for(register int d=0;d<8&&prime[d]<=tmp;++d){
            int cnt=0;
            while(!(tmp%prime[d]))tmp/=prime[d],++cnt;
            if(cnt>=2){bz=0;break;}
            else if(cnt)status[i]^=(1<if(!bz)continue;
        if(tmp==1)v[i].push_back(i);
        else v[tmp].push_back(i);
    }
}
int main(){
    pre();
    T=read();
    while(T--){
        n=read();k=read();
        memset(dp,0,sizeof(dp));
        dp[0][0]=1;
        for(register int i=2;i<=n;i++){         
            for(register int o=k;o>=1;--o)                                                                                                                                                                                                                                                                                                                                                                                                         
                for(register int j=v[i].size()-1;j>=0;j--){
                    if(v[i][j]>n)break;
                    register int sta=status[v[i][j]];
                    register int R=lim^sta;
                        for(register int r=R;;r=(r-1)&R){
                            (dp[o][r|sta]+=dp[o-1][r])%=Mod;
                            if(!r)break;
                        }
                }
        }
        int ans=0;
        for(register int i=1;i<=k;i++){
            for(register int j=0;j<=lim;j++){
                int t=dp[i][j];
                (ans+=t)%=Mod;
                if(i!=k)(ans+=t)%=Mod;
            }
        }
        cout<1<

你可能感兴趣的:(DP及DP优化)