[省选练习]S

[省选练习]六:状压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 \times (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\n",ans-1<0?ans-1+MOD:ans-1);
	return 0;
}

posted on 2018-12-26 16:49  ErkkiErkko  阅读(89)  评论(0编辑  收藏  举报