【2018/09/08】T2-状压dp-整数划分(WOJ 3922)

版权声明:虽然我只是个小蒟蒻但转载也请注明出处哦 https://blog.csdn.net/weixin_42557561/article/details/82530214

传送门

分析

好生优秀的一道题,暴力捞到90分???

来谈谈正解吧

我们很容易发现 n 分解出来的整数因为两两互质,所以他们的因数肯定也是互质的。所以若 n = p1^a1 * p2^a2 * ……*pk*ak,那么其分解出来的每一个整数n= x * y * z ,x要不就是囊括同一种的质因数的全部,要不就不含该种质因数。因为如果 x 含有几个p1,y含有几个p1,那么这两个数肯定不互质。现在我们就得到一个集合U= { p1^a1 ,p2^a2 , …,…,pk*ak } ,然后就来凑那些整数(集合的某个子集,这些子集互不相交且并集为U)。

由于一个  n 最大为1e18,其不同的质因数至多15个(把质数从小往大依次乘起来,乘到第15个质数的时候就超过1e18了),我们就可以状压dp了

特殊考虑一下S集合中出现1的情况,此时不用讨论1,只需要最后在总答案上乘以2即可

不存在分解的情况:n包含的质因数,S集合中不包含

这个状压dp的题就很优秀,并不是一眼就能从数据范围中看出是状压dp,而是需要经过推导,得出一个适合状压dp的数据范围

代码

#include<cstdio>
#include<algorithm>
#define in read()
#define N 100000
#define ll long long
using namespace std;
int k,m,pri[N],cnt=0,temp;
bool mark[N];
int a[N]; 
pair<int ,int> p[N];
int bit=0;
ll n,f[1<<20];
void prime(){
	for(int i=2;i<=N;++i){
		if(!mark[i]) pri[++cnt]=i;
		for(int j=1;j<=cnt&&pri[j]*i<=N;++j){
			mark[pri[j]*i]=1;
			if(i%pri[j]==0) break;
		}
	}
}
inline int read()
{
	char c=getchar(); int t=0;
	while (c<48 || c>57) c=getchar();
	while (c>47 && c<58)
		t=(t<<1)+(t<<3)+c-48,c=getchar();
	return t;
}
bool ok[N];
int v[N],t,num;
void divide(int x){//分解质因数
	temp=0;
	for(int i=1;i<=cnt&&1ll*pri[i]*pri[i]<=x;++i){
		if(x%pri[i]) continue;
		p[++temp]=make_pair(0,0);//first-->质因数的大小,second-->这个质因数的个数
		p[temp].first=pri[i];
		while(x%pri[i]==0){	p[temp].second++;x/=pri[i];	}
		if(!ok[pri[i]]) ok[pri[i]]=1,v[++t]=pri[i];//统计共有多少个不同的质因数
	}
	if(x>1) p[++temp]=make_pair(x,1),v[++t]=x;//最后一次不用判ok(出现与否)因为数组开不了那么大,后面再去重即可
	return;
}
bool check(int x){
	if(n%x) return 0;
	divide(x);
	for(int i=1;i<=temp;++i){
		ll mul=1;
		for(int j=1;j<=p[i].second;++j)
			mul*=1ll*p[i].first;
		if((n/mul)%p[i].first==0) return 0;//如果这个数x它所含有的单个质因数的个数小于n的,那么它肯定不能纳入考虑范围
	}
	return 1;
}
bool check_0(){
	ll hh=n;
	for(int i=1;i<=bit;++i)
		while(hh%v[i]==0) hh/=v[i];
	if(hh>1) return 1;//如果n还含有除以上那些出现在s集合中的质因数,那么肯定凑不出来
	return 0;
}
int cal(int x){//将合法的集合中x这个数,拆成二进制数,第i-1位取1表示v[i]这个质因数在x中出现了
	int res=0;
	for(int i=1;i<=bit;++i)
		if(x%v[i]==0) res|=(1<<i-1);
	return res;	
}
int main()
{
	prime();
	scanf("%lld",&n);k=in;
	int i,j,x,pow=1;
	for(i=1;i<=k;++i){
		x=in;
		if(x==1) {pow=2;continue;}
		if(check(x)) a[++num]=x;//num-->m,t-->num
	}
	sort(v+1,v+t+1);
	for(i=1;i<=t;++i)
		if(v[i]!=v[i+1]) v[++bit]=v[i];
	if(check_0()) {
		printf("0");
		return 0;
	}
	for(i=1;i<=num;++i)
		a[i]=cal(a[i]);
	f[0]=1;
	for(i=0;i<(1<<bit);++i)//枚举所有状态
		for(j=1;j<=num;++j)//枚举所有可以加的数
			if((i&a[j])==0&&i<a[j])//如果这个状态和当前这个数所含有的质因数不冲突,并且我们强制规定从小往大加
				f[i|a[j]]+=f[i];
	printf("%lld",f[(1<<bit)-1]*pow);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_42557561/article/details/82530214