[省选练习]六:状压DP
题意
给定一个正整数(N),数据满足(N)最多含六个不同的质因子。现在从一个空数列开始,把(N)的因数一个一个加到数列中,每次加入的数可能重复,但必须都大于(1)。每次添加新的数字的时候,需要保证这个数(x)与数列中已有的数至多一个有大于(1)的公因数。例如,当(N)等于(12156144)的时候,((42),(66,7,7,23,299,66),(42,12156144))是合法的,而数列((5,11))是不合法的,因为(5)不是(12156144)的因数,数列((66,13,143))也是不合法的,因为(143)与其他两个数有大于(1)的公因数。现在请你求出共有多少个不同的数列满足以上条件。特别地,两个数列不同,当且仅当他们的长度不同或某一处的数不同。
分析
大概是一道挺简单的题,不过状态设计十分新颖,用到了三进制状压。(一点都不新颖,自己这么鶸还不承认)
设(n)共有(cnt)种质因数,(sta)的前(cnt)位表示每个质因子在多少个选出数列的数中出现,容易发现这个数不会大于(2),所以可以使用三进制状压。(sta)的后(frac{cnt imes (cnt-1)}{2})位每位表示一对不同的质因数在这之后能否出现在同一个数中。(f[sta])的转移比较显然吧。
由于C++并没有自带三进制位运算符,所以在处理三进制数时我们可以把它暂存到数组里。
代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <algorithm>
#include <utility>
#include <vector>
#include <map>
#define rin(i,a,b) for(int i=(a);i<=(b);i++)
#define rec(i,a,b) for(int i=(a);i>=(b);i--)
#define trav(i,a) for(int i=head[(a)];i;i=e[i].nxt)
typedef long long LL;
using std::cin;
using std::cout;
using std::endl;
inline LL read(){
LL x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
}
const LL MOD=1e9+7;
LL n;
int cnt=-1,num[6],ano[6],forb[15],top;
std::vector<int> fac;
std::pair<int,int> trans[15];
std::map<int,LL> mp;
inline void getprimefactor(){
LL nn=n,lim=sqrt(n);
for(LL i=2;i<=lim;i++){
if(n%i) continue;
fac.push_back(0);cnt++;
while(n%i==0) fac[cnt]++,n/=i;
}
if(n!=1){
fac.push_back(1);cnt++;
}
std::swap(n,nn);
}
int main(){
n=read();
getprimefactor();
mp.clear();
mp[0]=1;
int usiz=pow(3,cnt+1)*(1<<((cnt+1)*cnt/2))-1;
int ii=0,jj=1;
rin(i,0,(cnt+1)*cnt/2-1){
trans[i]=std::make_pair(ii,jj);
jj++;if(jj>cnt) ii++,jj=ii+1;
}
LL ans=0;
rin(i,0,usiz){
if(mp.find(i)==mp.end()) continue;
ans+=mp[i];if(ans>=MOD) ans-=MOD;
int sta=(i>>((cnt+1)*cnt/2)),sat=(i&((1<<((cnt+1)*cnt/2))-1));
rin(j,0,cnt) num[j]=sta%3,sta/=3;
sta=0,top=0;
rin(j,0,cnt) if(num[j]<2) sta|=(1<<j);
rin(j,0,(cnt+1)*cnt/2-1) if((sat>>j)&1) forb[++top]=j;
for(int j=sta;j;j=((j-1)&sta)){
bool flag=0;
rin(k,1,top)
if(((j>>trans[forb[k]].first)&1)
&&((j>>trans[forb[k]].second)&1)){
flag=1;break;
}
if(flag) continue;
int nxtsta=j,nxtsat=0;
rin(k,0,cnt) ano[k]=(nxtsta&1),nxtsta>>=1;
nxtsta=0;int temp=1;
rin(k,0,cnt) nxtsta+=temp*(num[k]+ano[k]),temp*=3;
rin(k,0,(cnt+1)*cnt/2-1)
if((num[trans[k].first]&&ano[trans[k].second])
||(num[trans[k].second]&&ano[trans[k].first])||((sat>>k)&1))
nxtsat|=(1<<k);
int nxt=((nxtsta<<((cnt+1)*cnt/2))|nxtsat);
if(mp.find(nxt)==mp.end()) mp[nxt]=0;
LL temp2=1;
rin(k,0,cnt) if(ano[k]) temp2=temp2*fac[k]%MOD;
mp[nxt]=(mp[nxt]+mp[i]*temp2)%MOD;
}
}
printf("%lld
",ans-1<0?ans-1+MOD:ans-1);
return 0;
}