Codeforces Round #628 (Div. 2) E. Ehab's REAL Number Theory Problem(图论/bfs求无向图最小环)

题目

n(n<=1e5)个数,第i个数为ai(1<=ai<=1e6),保证ai最多有7个因子

找出这个数组的最短子序列,使得子序列内的所有元素的乘积是一个完全平方数

输出这个最短子序列的长度

思路来源

https://www.bilibili.com/video/av96374512 dls的B站讲解

https://codeforces.com/contest/1325/submission/73253372 dls的代码

题解

首先,若ai有3个质因子,则每个质因子二选一,共8种选择,说明因子>=8不成立,

以上,得出质因子个数<=2

此外,出现偶数次的素因子无意义,即p^{3}p是等价的,所以,只统计出现奇数次的素因子,

把数分为无奇数次素因子,一个奇数次素因子,两个奇数次素因子,即1,p,pq三类

①若出现1,则答案为1,特判

②若出现两个相同的p,则答案为2,特判

否则,需要找到形如ab bc ca这样的环,或者a ab bc c这样的环,

对于p*q,就将p与q连一条边,

而对于一个奇数次素因子,将p理解为1*p,将1与p连一条边,

由于p*q小的素因子一定<=1e3,所以枚举所有出现的<=1e3的素因子,bfs找到无向图的最小环

 

dfs由于搜索顺序的原因,不一定能找到最小环/最大环,

但bfs一定能找到每个根节点对应意义下的最小环,枚举每个根节点,复杂度O(nm)

但此题由于小的素因子的特殊性,最坏情况下为O(小于1e3的质数个数*1e5),故可行

bfs树中,对于u来说,若v已经入队,则环的大小为dep[u]+dep[v]+1

 

注意bfs树的写法,判掉v==fa的情形,从而找到不是前驱的访问过的点

第一种写法,开一个fa数组,避免无向图的反向边

第二种写法,记录v是由第i条边更新而来的,开一个vector<P>,第一维记录点号,第二维记录边号

由于数据范围宽松,是否离散化,均可通过此题

心得

bfs求无向图最小环,只适用于边长为1的情形,复杂度O(nm)

floyd求无向图最小环,适用于有边权的情形,复杂度O(n^3)

代码1(fa数组+离散化)

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10,INF=0x3f3f3f3f;
#define pb push_back
int n,v,mp[N],dep[N],fa[N],cnt[N],tot,ans;
bool in[N],ok;
set<int>p;
vector<int>fac;
vector<int>e[N];
void add(int u,int v){
	e[u].pb(v);
}
void bfs(int s){
	queue<int>q;
	q.push(s);
	dep[s]=0;in[s]=1;fa[s]=-1;
	int u;
	while(!q.empty()){
		u=q.front();q.pop();
		for(int v:e[u]){
			if(v==fa[u])continue; 
			if(in[v]){
				ans=min(ans,dep[u]+dep[v]+1);
			}
			else{
				dep[v]=dep[u]+1;
				in[v]=1;
				fa[v]=u;
				q.push(v);
			}
		}
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i){
		scanf("%d",&v); 
		fac.clear();
		for(int j=2;j*j<=v;++j){
			if(v%j==0){
				int x=0;
				while(v%j==0){
					v/=j;
					x++;
				}
				if(x&1)fac.pb(j),p.insert(j);
			}
		}
		if(v>1)fac.pb(v),p.insert(v);
		int len=fac.size();
		if(!len){
			puts("1");
			return 0;
		}
		if(len==1){
			if(!mp[fac[0]])mp[fac[0]]=++tot;
			add(0,mp[fac[0]]),add(mp[fac[0]],0);
			cnt[mp[fac[0]]]++;
		}
		else{
			if(!mp[fac[0]])mp[fac[0]]=++tot;
			if(!mp[fac[1]])mp[fac[1]]=++tot;
			add(mp[fac[0]],mp[fac[1]]),add(mp[fac[1]],mp[fac[0]]);
		}
	}
	for(int x:p){
		if(cnt[mp[x]]>=2){
			puts("2");
			return 0;
		}
	}
	ans=INF;
	for(int i=1;i<=1000;++i){
		if(!mp[i])continue;
		in[0]=0;
		for(int x:p)in[mp[x]]=0;
		bfs(mp[i]); 
	}
	if(ans==INF)puts("-1");
	else printf("%d\n",ans);
	return 0;
} 

代码2(vis数组+手写队列)

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10,INF=0x3f3f3f3f;
typedef pair<int,int>P;
#define rep(i,a,n) for(int i=(a);i<=(n);++i)
#define sci(x) scanf("%d",&(x))
#define fi first
#define se second
#define pb push_back
int n,v,dep[N],vis[N],cnt[N],ans;
int q[N];
bool ok;
set<int>p;
vector<int>fac;
vector<P>e[N];
void bfs(int s){
	int t=0;
	q[t++]=s;
	dep[s]=0;vis[s]=-2;
	int u;
	rep(i,0,t-1){
		u=q[i];
		for(auto x:e[u]){
			int v=x.fi,id=x.se;
			if(vis[v]!=-1 && vis[u]!=id){//v被访问过 且v不是u的前驱 
				ans=min(ans,dep[u]+dep[v]+1);
			}
			else if(vis[v]==-1){
				dep[v]=dep[u]+1;
				vis[v]=id;
				q[t++]=v; 
			}
		}
	}
}
int main(){
	sci(n);
	rep(i,1,n){
		sci(v);
		fac.clear();
		for(int j=2;j*j<=v;++j){
			if(v%j==0){
				int x=0;
				while(v%j==0){
					v/=j;
					x++;
				}
				if(x&1)fac.pb(j),p.insert(j);
			}
		}
		if(v>1)fac.pb(v),p.insert(v);
		int len=fac.size();
		if(!len){
			puts("1");
			return 0;
		}
		if(len==1){
			e[fac[0]].pb(P(1,i));
			e[1].pb(P(fac[0],i));
			cnt[fac[0]]++;
		}
		else{
			e[fac[0]].pb(P(fac[1],i));
			e[fac[1]].pb(P(fac[0],i));
		}
	}
	for(int x:p){
		if(cnt[x]>=2){
			puts("2");
			return 0;
		}
	}
	ans=INF;
	for(int i=1;i<=1000;++i){
		if(e[i].empty())continue;
		vis[1]=-1;
		for(auto x:p)vis[x]=-1;//被更新的边号 
		bfs(i); 
	}
	if(ans==INF)puts("-1");
	else printf("%d\n",ans);
	return 0;
} 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值