LOJ#3077. 「2019 集训队互测 Day 4」绝目编诗

VI.LOJ#3077. 「2019 集训队互测 Day 4」绝目编诗

神题。

乍一看好像和虚树半毛钱关系都没有呀?没关系,过亿会就有了。

我们不妨先从暴力开始想起。

暴力怎么写?暴力怎么写?加边加边加边,搜就完事了。

没错,这里的暴力就是爆搜——搜出所有环来,然后判断是否有两个环长度相等即可。

但是这里的暴力也需要一些艺术——因为我们要不重不漏地统计所有的环。我们得加上这几条限制:

  1. 爆搜时,只搜从一个点出发再回到该点本身的简单环,不包括 \(\rho\) 型甚至更诡异形状的。

  2. 既然是简单环,我们就不允许经过同一个点两次。

  3. 为了避免将长度为 \(2\) 的简单环也计入,我们还要记录环上前一个节点,避免走反边回到上一个节点。

这样,我们就保证可以搜出所有简单环。但是,一个简单环可能被重复搜出。更具体地说,对于一个长度为 \(x\) 的简单环,其会旋转 \(x\) 次,翻转 \(1\) 次,总计被搜出 \(2x\) 次。因此,当我们发现某一个长度的环出现了至少 \(2x+1\) 次,才可以判出现了长度相同的环。

暴力的复杂度是指数级别的,期望得分 \(60\%\)

代码:

//Version: Completely brute solution
#include<bits/stdc++.h>
using namespace std;
int n,m,sz[10100],dep[10100];
vector<int>v[10100];
void dfs(int x,int fa){
	dep[x]=dep[fa]+1;
	for(auto y:v[x])if(y!=fa){
		if(dep[y]){
			if(dep[y]!=1)continue;
			sz[dep[x]]++;
			if(sz[dep[x]]>2*dep[x]){puts("Yes");exit(0);}
			continue;
		}
		dfs(y,x);
	}
	dep[x]=0;
}
int main(){
	scanf("%d%d",&n,&m);
	if(m>=2*n){puts("Yes");return 0;}
	for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	for(int i=1;i<=n;i++)dfs(i,0);
	puts("No");
	return 0;
}

考虑优化。

首先,众所周知,一个 \(n\) 个点的简单图,其最大简单环的长度最多只能为 \(n\)。又因为不存在长度为 \(1,2\) 的简单环,所以若一个简单图不存在长度相等的简单环,其所拥有的简单环数量最多是 \(3\sim n\) 各一个,共 \(n-2\) 个。

考虑求出该图的任意一极大生成森林,其边数最多是 \(n-1\) 条。考虑往上加任意一条边,环数都必定增加至少 \(1\)。故添加至多 \(n-2\) 条边后,\(3\sim n\) 所有长度的环都出现了。再加任意一条边,都会出现长度相同的环。因此,不存在长度相同环的图,其 \(m\) 最大只能是 \((n-1)+(n-2)=2n-3\),超过此界直接判 Yes 即可。

于是 \(m\) 就被压缩到了和 \(n\) 同级。

下面我们加入第二个优化。因为 No 的最多情况下,环的总长是 \(3\sim n\) 各出现一次,共 \(n^2\) 级别。因此我们考虑仅访问环边,复杂度就是 \(n^3\) 的(因为每个环都会被访问 \(2x\) 次)。如何做到呢?很简单,只需在每次到达一个新节点时,\(O(n+m)\)dfs 一下看能不能回到起点,不能就直接剪枝剪掉即可。这样子,复杂度就被优化到了严格 \(O(n^4)\),再也不是什么玄学的指数复杂度了。

期望得分 \(80\%\)

代码:

//Version: improved brute solution (n^4)
#include<bits/stdc++.h>
using namespace std;
int n,m,sz[10100],dep[10100],vis[10100],id;
vector<int>v[10100];
bool reach(int x,int fa,int O){
	vis[x]=O;
	for(auto y:v[x])if(y!=fa){
		if(dep[y]==1)return true;
		if(dep[y]||vis[y]==O)continue;
		if(reach(y,x,O))return true;
	}
	return false;
}
void dfs(int x,int fa){
	dep[x]=dep[fa]+1;
	if(!reach(x,fa,++id)){dep[x]=0;return;}
	for(auto y:v[x])if(y!=fa){
		if(dep[y]){
			if(dep[y]!=1)continue;
			sz[dep[x]]++;
			if(sz[dep[x]]>2*dep[x]){puts("Yes");exit(0);}
			continue;
		}
		dfs(y,x);
	}
	dep[x]=0;
}
int main(){
	scanf("%d%d",&n,&m);
	if(m>=2*n){puts("Yes");return 0;}
	for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	for(int i=1;i<=n;i++)dfs(i,0);
	puts("No");
	return 0;
}

下面我们祭出最终优化。

明显,\(m\leq2n-3\) 这个限制是极松的。能否找到更紧的限制?

我们来考虑任何一张无长度相等的环的图。考虑极端情况,此时 \(3\sim n\) 的环各出现一次。

我们此时来删掉一些边。考虑对于每条边,都有 \(\dfrac{1}{\sqrt{n}}\) 的概率将其删掉。则因为 \(m,n\) 同级,所以期望删掉的边的条数是 \(O\Big(\dfrac{m}{\sqrt{n}}\big)=O(\sqrt{n})\) 级别的。

现在,考虑一长度为 \(l\) 的环。在删边结束后,该环幸存的概率是 \(\Big(1-\dfrac{1}{\sqrt{n}}\Big)^l\)。显然,每个环剩余的概率都是独立的,故全图剩余环的期望就是 \(\sum\limits_{l=3}^n\Big(1-\dfrac{1}{\sqrt{n}}\Big)^l\),是等比数列求和的形式。又因为公比 \(q=1-\dfrac{1}{\sqrt{n}}\),故 \(q\in(0,1)\),所以该式 \(<\dfrac{1}{1-q}=\sqrt{n}\)

因此,我们在删掉期望 \(\sqrt{n}\) 条边后,期望剩余 \(\sqrt{n}\) 个环。又因为期望涵盖了一切可能,所以定存在一种删边方案,使得删边后剩余环数量不超过 \(\sqrt{n}\)。此时,再删去至多 \(\sqrt{n}\) 条边即可消去剩余的 \(\sqrt{n}\) 个环。两者结合起来,即对于任一无长度相等的环的图,删去至多 \(2\sqrt{n}\) 条边即可消去一切环,换句话说,得到一个边数至多为 \(n-1\) 的森林。正因如此,我们就证明了无长度相等的环的图具有的边数至多是 \(n+2\sqrt{n}\) 级别的。这样,对于 \(m>n+2\sqrt{n}\),也可以直接判 Yes 了。

(事实上,这个界也不是实的,但对于本题来说够用了)

考虑求出原图的任意生成森林。这时,非树边至多就只有 \(2\sqrt{n}\) 条,所牵涉到的点最多只有 \(4\sqrt{n}\) 个。对这 \(4\sqrt{n}\) 个点建出虚树,然后虚树上的边便带了权。再把非树边加入虚树,便得到了一张点数至多 \(4\sqrt{n}\) 的图。同时,因为虚树是一棵树,边数是 \(\sqrt{n}\) 级别的,再加入 \(\sqrt{n}\) 级别的非树边,总边数仍是 \(\sqrt{n}\) 级别的。这样,我们便得到了一张点、边均为 \(\sqrt{n}\) 级别的带权图。明显我们上述 \(n^4\) 的算法对带权图仍然适用,因此复杂度便被优化到了 \(n^2\)

需要注意的是,在加入非树边后,该虚树便可能出现重边。这时不能简单记录环上上一个节点,而应记录环上上一条边。

同时,因为边带了权,长度为 \(x\) 的环上不一定就有恰好 \(x\) 个点,也就不一定出现恰 \(2x\) 次了。为了避免这种情况,我们记录当前长度为 \(x\) 的环上点数,若搜出的新环的长度与之不同,明显是两个不同的环,可以直接判 Yes 了;否则,因为知晓了点数,就和之前一样统计即可。

时间复杂度 \(O(n^2)\)。因为压根跑不满,再加上LOJ逆天的评测姬,轻松跑过。期望得分 \(100\%\)

.代码:

//Version: correct solution (n^2)
#include<bits/stdc++.h>
using namespace std;
int n,m;
namespace OriginalGraph{
	int dsu[10100];
	int find(int x){return dsu[x]==x?x:dsu[x]=find(dsu[x]);}
	bool merge(int x,int y){
		x=find(x),y=find(y);
		if(x==y)return false;
		dsu[x]=y;return true;
	}
}
using namespace OriginalGraph;
namespace ImprovedGraph{
	int tms[10100],sz[10100],dep[310],ped[310],vis[310],id,head[310],cnt;
	struct node{int to,next,val;}edge[40100];
	void ae(int u,int v,int w){
//		printf("%d %d %d\n",u,v,w);
		edge[cnt].next=head[u],edge[cnt].to=v,edge[cnt].val=w,head[u]=cnt++;
		edge[cnt].next=head[v],edge[cnt].to=u,edge[cnt].val=w,head[v]=cnt++;
	}
	bool reach(int x,int fe,int O){
		vis[x]=O;
		for(int i=head[x],y;i!=-1;i=edge[i].next)if((i^fe)!=1){
			y=edge[i].to;
			if(!dep[y])return true;
			if(dep[y]!=-1||vis[y]==O)continue;
			if(reach(y,i,O))return true;
		}
		return false;
	}
	void circ(int x,int fe){
		if(!reach(x,fe,++id))return;
		for(int i=head[x],y;i!=-1;i=edge[i].next)if((i^fe)!=1){
			y=edge[i].to;
			if(dep[y]!=-1){
				if(dep[y])continue;
				if(sz[dep[x]+edge[i].val]){
					if(ped[x]+1!=sz[dep[x]+edge[i].val]){puts("Yes");exit(0);}
					tms[dep[x]+edge[i].val]++;
					if(tms[dep[x]+edge[i].val]>2*sz[dep[x]+edge[i].val]){puts("Yes");exit(0);}
				}else sz[dep[x]+edge[i].val]=ped[x]+1,tms[dep[x]+edge[i].val]++;
				continue;
			}
			dep[y]=dep[x]+edge[i].val,ped[y]=ped[x]+1,circ(y,i),dep[y]=-1,ped[y]=0;
		}
	}	
}
using namespace ImprovedGraph;
namespace SpanningTree{
	vector<int>v[10100];
	vector<pair<int,int> >e;
	int dis[10100],num[10100],tot,las[10100];
	void dfs(int x,int fa){
//		printf("%d\n",x);
		dis[x]=dis[fa]+1;
		if(las[x])num[x]=++tot;
		for(auto y:v[x]){
			if(y==fa)continue;
			dfs(y,x);
			if(!las[y])continue;
			if(las[x]){
				if(las[x]==x)ae(num[x],num[las[y]],dis[las[y]]-dis[x]);
				else num[x]=++tot,ae(num[x],num[las[x]],dis[las[x]]-dis[x]),ae(num[x],num[las[y]],dis[las[y]]-dis[x]),las[x]=x;
			}else las[x]=las[y];
		}
	}
}
using namespace SpanningTree;
int main(){
	scanf("%d%d",&n,&m),memset(head,-1,sizeof(head));
	if(m>=2*sqrt(n)+n){puts("Yes");return 0;}
	for(int i=1;i<=n;i++)dsu[i]=i;
	for(int i=1,x,y;i<=m;i++){
		scanf("%d%d",&x,&y);
		if(merge(x,y))v[x].push_back(y),v[y].push_back(x);
		else e.push_back(make_pair(x,y)),las[x]=x,las[y]=y;
	}
	for(int i=1;i<=n;i++)if(!dis[i])dfs(i,0);
	for(auto i:e)ae(num[i.first],num[i.second],1);
	memset(dep,-1,sizeof(dep));
	for(int i=1;i<=tot;i++)dep[i]=0,circ(i,-1),dep[i]=-1;
	puts("No");
	return 0;
}

原文地址:https://www.cnblogs.com/Troverld/p/14621483.html