Codeforces 1235E Ehab's REAL Number Theory Problem 题解

博客观赏效果更佳

考场上想出了思路,但是没能写出来代码,实在可惜

题意简述

n ≤ 1 e 5 n\le 1e5 n1e5 个正整数,每个数值域都是 [ 1 , 1 0 6 ] [1,10^6] [1,106],并且最多有 7 7 7 个因数。 请你求出最少选多少个数能使得乘积是平方数。

思路

最多 7 7 7 个因数 ↔ \leftrightarrow 最多两个质因数

而且对于一个质因数,显然我们还可以把它的指数膜个 2 2 2。如果指数到最后是 0 0 0,那么我们直接扔掉这个质因数;如果两个质因数指数都是 0 0 0,那好了,直接输出 1 1 1,结束程序。

然后就是一个数等于 p p p 或者 p × q p\times q p×q 的情况了。 p p p 的情况就加边 ( 1 , p ) (1,p) (1,p) p × q p\times q p×q 的情况就加边 ( p , q ) (p,q) (p,q),然后求一个最小环即可。 而且我们发现,一个环上顶多有一个点在 > 1000 >1000 >1000 ,那么我们可以以 1   1000 1~1000 1 1000 中的点为起点,分别使用 B F S BFS BFS 找环法。这样只能找到包含某个点的最小环,但这足够了,因为这个环必然包含一个编号小于等于 1000 1000 1000 的点。而我们只会在质数间连边,所以我们的实际复杂度比你想象的要小很多!

怎么想到的

主要就是如何转换为最小环那一步。首先这个相当于我们选若干个数,分解成 1 × p 1\times p 1×p 或者 p × q p\times q p×q 的形式,然后几个数覆盖起来,每个质数 p p p 都得被算偶数次( 0 0 0 也是偶数,不算到也是可以的)。那这样我们的总乘积中,每个质因数都是偶数次方,那就是完全平方数了。

考虑到一个简单的环上的每个点度数都是 2 2 2,所以我们就能想到把它建成图,然后跑最小环。那么有小朋友(无 中 生 友)就问了,会不会有环上的点度数为 4 , 6 , 8 4,6,8 4,6,8,甚至更大的偶数呢?其实这个就相当于一个点被算了很多次嘛,但是我们要求的是“最少多少个数的积是平方数”,也就是我们要让环尽量的小。容易证明,一个最小的环肯定是简单环。(因为如果你是一个复合的环,那么肯定能拆出来若干的简单环,答案更优)

代码

{% fold %}

#include <bits/stdc++.h>
using namespace std;
namespace Flandre_Scarlet
{
	#define N 1666666
	#define F(i,l,r) for(int i=l;i<=r;++i)
	#define D(i,r,l) for(int i=r;i>=l;--i)
	#define Fs(i,l,r,c) for(int i=l;i<=r;c)
	#define Ds(i,r,l,c) for(int i=r;i>=l;c)
	#define MEM(x,a) memset(x,a,sizeof(x))
	#define FK(x) MEM(x,0)
	#define Tra(i,u) for(int i=G.Start(u),v=G.To(i);~i;i=G.Next(i),v=G.To(i))
	#define p_b push_back
	#define sz(a) ((int)a.size())
	#define iter(a,p) (a.begin()+p)
	int I()
	{
	    int x=0;char c=getchar();int f=1;
	    while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
	    while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
	    return (x=(f==1)?x:-x);
	}
	void Rd(int cnt,...)
	{
	    va_list args; va_start(args,cnt);
	    F(i,1,cnt) {int* x=va_arg(args,int*);(*x)=I();}
	    va_end(args);
	}
	class Graph
	{
	    public:
	        int head[N];
	        int EdgeCount;
	        struct Edge
	        {
	            int To,Next;
	        }Ed[N<<1];
	        void clear(int _V=N,int _E=N<<1) 
	        {
	            memset(Ed,-1,sizeof(Edge)*(_E));
	            memset(head,-1,sizeof(int)*(_V));
	            EdgeCount=-1;
	        }
	        void AddEdge(int u,int v)
	        {
	            Ed[++EdgeCount]=(Edge){v,head[u]};
	            head[u]=EdgeCount;
	        }
	        void Add2(int u,int v) {AddEdge(u,v);AddEdge(v,u);}
	        int Start(int u) {return head[u];}
	        int To(int u){return Ed[u].To;}
	        int Next(int u){return Ed[u].Next;}
	}G;
	
	bool notp[N]; int primes[N];;
	vector<int> pd[N];
	//prime divisors 的简写,保存质因数
	void Init()
	{
		notp[1]=1;
		int n=1e6;
		F(i,2,n) if (!notp[i]) 
		{
			primes[++primes[0]]=i;
			pd[i].p_b(i);
			Fs(j,2*i,n,j+=i) 
			{
				if (sz(pd[j])<2) pd[j].p_b(i);  
				notp[j]=1;
				// 题目保证质因数个数<=2,只要存前两个即可,空间复杂度O(2n)
			}
		}
		
	}
	int n,a[N];
	void Input()
	{
		n=I(); F(i,1,n) a[i]=I();
		sort(a+1,a+n+1);
	}

	int  cnt(int p,int x) //计算 x 有多少个因子 p
	{
		int ans=0; 
		while(x%p==0) {ans^=1; x/=p;}
		return ans;
	}

	int ans=0x3f3f3f3f;
	bool added[N]; // 判一下重边
	void add(int a,int b)
	{
		if (added[a*b]) {ans=min(ans,2); return;} //有重边,那就能组成一个二元环,答案更新为 2
		added[a*b]=1; G.Add2(a,b);
	}
	void Factor(int x)
	{
		if (x==1) {puts("1"); exit(0);}
		if (sz(pd[x])==1)
		{
			int p=pd[x][0],pp=cnt(p,x);
			if (pp==0) {ans=min(ans,1);}
			else {add(1,p);}
		}
		else //pd[x].size()==2
		{
			int p=pd[x][0],q=pd[x][1];
			int pp=cnt(p,x),qq=cnt(q,x);
			if (pp==0 and qq==0) {ans=min(ans,1);}
			else if (pp==1 and qq==1) {add(p,q);}
			else if (pp==0 and qq==1) {add(1,q);}
			else if (pp==1 and qq==0) {add(1,p);}
		}
	}
	int Q[N],vis[N],dis[N],fa[N]; int Round=0;
	void BFS(int s) // 包含 s 的最小环
	{
		int head=1,tail=1;
		++Round; 
		Q[tail]=s; dis[s]=0;
		while(head<=tail)
		{
			int u=Q[head++];
			vis[u]=Round;
			Tra(i,u) 
			{
				if (vis[v]!=Round) {dis[v]=dis[u]+1; vis[v]=Round; Q[++tail]=v; fa[v]=u;}
				else 
				{
					if (v!=fa[u]) ans=min(ans,dis[u]+dis[v]+1);
					// 此时 s->u,u->v,v->s 组成一个环,三个部分长度分别是 dis[u],1,dis[v],总长度就是 dis[u]+dis[v]+1
					// 注意不能搜回前驱节点
				}
			}
		}
	}
	void Soviet()
	{
		G.clear(); 
		F(i,1,n) Factor(a[i]);
		if (ans<=2) {printf("%d\n",ans); return;}
		BFS(1); F(i,1,180) BFS(primes[i]);
		printf("%d\n",ans==0x3f3f3f3f?-1:ans);
	}

	#define Flan void
	Flan IsMyWife()
	{
		Init();
		Input();
		Soviet();
	}
}
int main()
{
	Flandre_Scarlet::IsMyWife();
	getchar();getchar();
	return 0;
}

{% endfold %}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值