「2019 集训队互测 Day 4」绝目编诗 题解
本文网址:https://www.cnblogs.com/zsc985246/p/17314934.html ,转载请注明出处。
题目大意
给出一个 个点 条边的简单无向图,判断是否存在两个长度相同的简单环。
。
思路
考虑暴力怎么做。
我们可以对于每个点,搜出包含这个点的环。
那么我们就有了一个玄学复杂度的暴力。考虑优化。
因为一个无向图的简单环长度只可能在 的区间内,一共 个,所以长度之和为 级别。因为每个环我们会遍历两倍长度次,所以如果我们只访问环上的边,我们就可以达到 的复杂度。
为了只遍历环边,我们每到一个点都用一个 的 dfs 判断当前点是否可能构成环,即判断当前点是否可以不通过搜索栈中的节点到达起点。这样,我们的整体复杂度就是 的。
这样显然不能过,而且暴力已经不好优化了,所以考虑减少 的规模。
考虑一个图简单环长度两两不同时,最多有多少条边。
直接想比较困难,考虑一颗树最多可以加入多少条边,使得简单环长度两两不同。
首先对于一颗树,每加入一条边至少会产生一个简单环。
要让加入的边数最大,我们肯定要让每一条边只形成一个简单环。
根据简单环的定义,我们发现,只有两个简单环交点个数小于等于 时,它们才不会构成新的简单环。
那么一个简单的构造方法就是先让一个点作为所有简单环的交点,然后在这个点上挂环。
考虑现在有 个点,我们选定 号节点作为交点。
我们挂一个长度为 的环,需要 个点和 条边。
我们从长度为 的环开始,长度从小到大依次往上挂。
那么假设我们挂完了长度小于等于 的环,我们用的点数(包括 号节点) 为 ,需要的边数 就是 。
最好的情况肯定是所有点正好用完,也就是 。
这时我们可以解得 ,推出 。
那么我们就可以知道,一个 个点的图简单环长度两两不同,最多有 条边。
那如果边数大于这个值,我们就可以直接输出 Yes
了。
现在我们把边的范围缩小到了 级别。如果我们在图上找到一个 dfs 树,那么非树边就是 条。
我们可以对这 条边连的点建虚树,这样我们的点数就到达了 的级别。
那么这时我们再使用上方的暴力算法就可以达到 的优秀复杂度。
那么因为 ,加上 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;
}
尾声
网上还有一种关于最多边数的求解,使用的是期望法,最后结果是 ,这里也简单说一下。
考虑随机删去一个 个点的图的 条边。一个长度为 的简单环能够幸存的概率 。
那么假设这个图长度在 间的所有简单环都各有一个,那么这个图剩下的简单环数的期望 。
这是等比数列的求和,公比 ,所以原式为 。
因为这是期望,所以一定有一种方式让剩下环的个数为 。那再删去 条边就可以删去所有简单环了。
最后我们删掉了 条边,使得这个图没有简单环,那么这个图的边数就是 。
因为这个图有所有长度的简单环,所以再加一条边就有两个长度相同的简单环了。
如果你发现了问题,你可以直接回复这篇题解
如果你有更好的想法,也可以直接回复!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现