「2019 集训队互测 Day 4」绝目编诗 题解

本文网址:https://www.cnblogs.com/zsc985246/p/17314934.html ,转载请注明出处。

题目大意

给出一个 n 个点 m 条边的简单无向图,判断是否存在两个长度相同的简单环。

1n104,1m106

思路

考虑暴力怎么做。

我们可以对于每个点,搜出包含这个点的环。

那么我们就有了一个玄学复杂度的暴力。考虑优化。

因为一个无向图的简单环长度只可能在 [3,n] 的区间内,一共 n2 个,所以长度之和为 n2 级别。因为每个环我们会遍历两倍长度次,所以如果我们只访问环上的边,我们就可以达到 O(n3) 的复杂度。

为了只遍历环边,我们每到一个点都用一个 O(n) 的 dfs 判断当前点是否可能构成环,即判断当前点是否可以不通过搜索栈中的节点到达起点。这样,我们的整体复杂度就是 O(n4) 的。

这样显然不能过,而且暴力已经不好优化了,所以考虑减少 n 的规模

考虑一个图简单环长度两两不同时,最多有多少条边

直接想比较困难,考虑一颗树最多可以加入多少条边,使得简单环长度两两不同。

首先对于一颗树,每加入一条边至少会产生一个简单环。

要让加入的边数最大,我们肯定要让每一条边只形成一个简单环。

根据简单环的定义,我们发现,只有两个简单环交点个数小于等于 1,它们才不会构成新的简单环。

那么一个简单的构造方法就是先让一个点作为所有简单环的交点,然后在这个点上挂环

考虑现在有 n 个点,我们选定 1 号节点作为交点。

我们挂一个长度为 x 的环,需要 x1 个点和 x 条边。

我们从长度为 3 的环开始,长度从小到大依次往上挂。

那么假设我们挂完了长度小于等于 t 的环,我们用的点数(包括 1 号节点)cnt1+ti=3i1=t1i=1i=t×(t1)2,需要的边数 tot 就是 cnt1+t2=cnt+t3

最好的情况肯定是所有点正好用完,也就是 cnt=t×(t1)2=n

这时我们可以解得 t=1+1+8n2,推出 tot=n+1+1+8n23

那么我们就可以知道,一个 n 个点的图简单环长度两两不同,最多有 n+1+1+8n23 条边

那如果边数大于这个值,我们就可以直接输出 Yes 了。

现在我们把边的范围缩小到了 n+n 级别。如果我们在图上找到一个 dfs 树,那么非树边就是 n 条。

我们可以对这 n 条边连的点建虚树,这样我们的点数就到达了 n 的级别。

那么这时我们再使用上方的暴力算法就可以达到 O(n2)优秀复杂度。

那么因为 n104,加上 dfs 根本跑不满,就可以直接通过本题了。

代码实现

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define pb push_back
#define Yes printf("Yes\n")
#define No printf("No\n")
const ll N=1e6+10;
using namespace std;

ll n,m,k;
vector<ll>e[N];//图
ll fa[N];//dfs树上的父亲
ll id[N];//在虚树上属于的点的编号
ll dep[N];//深度
ll dis[N];//距离

//虚树
ll tot=1,fir[N],nxt[N],v[N],w[N];
void add_edge(ll x,ll y,ll z){v[++tot]=y;w[tot]=z;nxt[tot]=fir[x];fir[x]=tot;}
void add(ll x,ll y,ll z){add_edge(x,y,z);add_edge(y,x,z);}

void dfs(ll x,ll father){
	fa[x]=father;
	dep[x]=dep[fa[x]]+1;
	ll flag=0;
	//flag=-1表示x是LCA,需要加入虚树
	//flag>0表示x不是LCA,但子树内有点在虚树中
	//flag=0表示x子树内没有点在虚树中
	for(ll y:e[x]){
		if(y==fa[x])continue;
		if(dep[y]){//非树边
			if(dep[x]<dep[y])continue;//保证只连一次
			if(!id[x])id[x]=x;//分配编号
			if(!id[y])id[y]=y;//分配编号
			add(id[x],id[y],1);//连边
			continue;
		}
		dfs(y,x);//递归
		if(id[y]){//y子树在虚树中
			if(flag)flag=-1;//x节点是LCA
			else flag=id[y];//标记y在虚树上的编号
		}
	}
	if(flag==-1&&!id[x])id[x]=x;//分配编号
	if(id[x]){//在虚树中
		for(ll y:e[x]){
			if(fa[y]==x&&id[y]){//是x的儿子且在虚树中
				add(id[x],id[y],dep[id[y]]-dep[id[x]]);//连边
			}
		}
	}else id[x]=flag;
}

ll vis[N];//标记
ll cnt[N];//长度为i的环的个数
bool check(ll x,ll k){//k为标记编号
	if(dep[x]==1)return true;//到达根节点
	vis[x]=k;
	for(ll i=fir[x];i;i=nxt[i]){
		ll y=v[i],z=w[i];
		if(vis[y]==k)continue;
		if(dep[y]>1)continue;//走过的点不走
		if(check(y,k))return true;
	}
	return false;
}

void solve(ll x,ll in){//O(n^4)暴力
	if(!check(x,++k))return;//不能构成环
	for(ll i=fir[x];i;i=nxt[i]){
		ll y=v[i],z=w[i];
		ll t=z+dis[x];//到根节点的距离
		if(i==in)continue;//入边跳过
		if(dep[y]==1){//可以到达根节点
			cnt[t]++;//t就是环长
			if(cnt[t]>2*dep[x]){//长度大于深度的2倍 
				Yes;
				exit(0);
			}
		}
		if(dep[y])continue;
		dep[y]=dep[x]+1;//更新深度
		dis[y]=t;//更新距离
		solve(y,i^1);
		dep[y]=0;//回溯
	}
}

void mian(){
	
	ll ans=0;
	
	scanf("%lld%lld",&n,&m);
	For(i,1,m){
		ll x,y;
		scanf("%lld%lld",&x,&y);
		e[x].pb(y),e[y].pb(x);
	}
	//结论
	if(m>n+(sqrt(1+8*n)+1)/2-3){
		Yes;
		return;
	}
	//建虚树
	For(i,1,n){
		if(!dep[i])dfs(i,0);
	}
	
	For(i,1,n)dep[i]=0;//初始化
	For(i,1,n){
		if(id[i]==i){
			dep[i]=1;
			dis[i]=0;
			solve(i,0);
			dep[i]=0;
		}
	}
	No;
	
}

int main(){
	int T=1;
//	scanf("%d",&T);
	while(T--)mian();
	return 0;
}

尾声

网上还有一种关于最多边数的求解,使用的是期望法,最后结果是 n+2n1,这里也简单说一下。

考虑随机删去一个 n 个点的图的 n 条边。一个长度为 i 的简单环能够幸存的概率 pi=(11n)i

那么假设这个图长度在 [3,n] 间的所有简单环都各有一个,那么这个图剩下的简单环数的期望 E=ni=3(11n)i

这是等比数列的求和,公比 q=11n,所以原式为 q3×1qn21q=(q3qn+1)×n<n

因为这是期望,所以一定有一种方式让剩下环的个数为 n1。那再删去 n1 条边就可以删去所有简单环了。

最后我们删掉了 2n1 条边,使得这个图没有简单环,那么这个图的边数就是 n+2n1

因为这个图有所有长度的简单环,所以再加一条边就有两个长度相同的简单环了。


如果你发现了问题,你可以直接回复这篇题解

如果你有更好的想法,也可以直接回复!

posted @   zsc985246  阅读(256)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示