集合划分 partition.cpp

31 篇文章 0 订阅
29 篇文章 0 订阅

【一句话题意】给定一个包含N 个非负整数的集合A,请将A 分成两个子集P、Q,且使得 g c d ( Π P i Π Q i ) = = 1 gcd(\Pi P_i \Pi Q_i)==1 gcd(ΠPiΠQi)==1。请计算这样的划分方法总数mod1000000007 后的值。 n<=1e6

【分析】一道可做的数论题。按质因数划分,P和Q中不能有相同的质因数,拥有相同质因数的数必须放在同一个集合。将每个数分解质因数后,将每个数和质因数之间连一个双向边,显然的是每个连通块都必须放在同一个集合。考虑方案时,从1到n-1枚举放入集合P的连通块的组合方案数就是 C 1 n + C 2 n + . . . . . + C n − 1 n C^n_1+C^n_2+.....+C^n_{n-1} C1n+C2n+.....+Cn1n
这里有个小技巧: C 0 n + C 1 n + . . . . . . + C n − 1 n + C n n = 2 n C^n_0+C^n_1+......+C^n_{n-1}+C^n_n=2^n C0n+C1n+......+Cn1n+Cnn=2n(每个数都有选或不选两种情况)
所以方案数就是2n-2

【code】

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define rint register int
using namespace std;
const int maxn=1e5+100;
const int N=1e6+100;
const int mod=1e9+7;
int n,a[maxn];

int fac[N],prime[N],cnt;
struct Prime{
	int id,nxt;
}p1[maxn*18];
int h1[N],u1[N],tot1;

struct node{
	int id,nxt;
}p2[maxn*18];
int h2[maxn],u2[maxn],tot2;
inline void read(int &x){
	x=0;char tmp=getchar();
	while(tmp<'0'||tmp>'9') tmp=getchar();
	while(tmp>='0'&&tmp<='9') x=(x<<1)+(x<<3)+tmp-'0',tmp=getchar();
}
void prework(){
	for(int i=2;i<=1e6;i++){
		if(!fac[i]) fac[i]=++cnt,prime[cnt]=i;
		for(int j=1;i*prime[j]<=1e6&&j<=cnt;j++){
			fac[i*prime[j]]=j;
			if(j==fac[i]) break;
		}
	}
}
inline void add_p1(int x,int y){
	p1[tot1].id=y;
	p1[tot1].nxt=h1[x];
	h1[x]=tot1++;
}
inline void add_p2(int x,int y){
	p2[tot2].id=y;
	p2[tot2].nxt=h2[x];
	h2[x]=tot2++;
}
void dfs(int x,int d){
	if(d==1){
		u1[x]=1;
		for(int i=h1[x];i!=-1;i=p1[i].nxt){
			if(!u2[p1[i].id]) dfs(p1[i].id,2);
		}
	}
	else if(d==2){
		u2[x]=1;
		for(int i=h2[x];i!=-1;i=p2[i].nxt){
			if(!u1[p2[i].id]) dfs(p2[i].id,1);
		}
	}
}
inline int pw(rint x,rint p){
	rint ret=1;
	while(p>0){
		if(p&1) ret=1ll*ret*x%mod;
		x=1ll*x*x%mod,p>>=1;
	}
	return ret;
}
int main(){
	freopen("partition.in","r",stdin);
	freopen("partition.out","w",stdout);
	prework();
	int T;cin>>T;
	while(T--){
		cin>>n;
		for(rint i=1;i<=n;i++)
			read(a[i]);
		memset(h1,-1,sizeof(h1));
		memset(h2,-1,sizeof(h2));
		memset(u1,0,sizeof(u1));
		memset(u2,0,sizeof(u2));
		tot1=tot2=0;
		for(rint i=1;i<=n;i++){//nlogn
			int x=a[i];
			while(x>1){
				int d=fac[x];
				add_p1(d,i);
				add_p2(i,d);
				while(fac[x]==d){
					x/=prime[d];
				}
			}
		}
		int ans=0;
		for(rint i=1;i<=n;i++){
			if(!u2[i]){
				ans++;
				dfs(i,2);
			}
		}
		cout<<(pw(2,ans)-2+mod)%mod<<endl;
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值