[2019 集训队互测 Day4] 绝目编诗
一、题目
二、解法
谢谢这个大佬的博客,讲得真的挺好的。
做的时候脑抽了,连暴搜都不会,我们先来考察一种有前途的暴搜。
首先枚举起点,然后尝试搜出一条到起点的回路(就是最暴力的方法,枚举下一个点然后回溯)。搜到每一个点的时候判断,如果它没有构成环的可能(即不通过在搜索栈中的节点到达起点),那么直接退出。
分析一下这种方法的时间复杂度,首先根据抽屉原理,环的个数不超过 \(n\) 个,最坏的情况是 \(3\sim n\) 各分布一个。考虑一个长度为 \(i\) 个环会被搜到 \(2i\) 次,每次搜到都需要走 \(i\) 个点,所以是 \(\sum_{i=3}^n2i^2=O(n^3)\);此时再乘上检查的复杂度,那么我们得到了一个稳定 \(O(n^4)\) 的算法。稍加随机化和掐表随便过前四个包,甚至可能直接过掉本题。
有一个神仙结论是 \(m>n+2\sqrt n\) 一定有解,考虑随机删 \(\sqrt n\) 条边期望剩下的环个数:
\[\sum_{i=3}^n(1-\frac{1}{\sqrt n})^i<\frac{1}{1-(1-\frac{1}{\sqrt n})}=\sqrt n
\]
那么一定存在一种删边使得剩下环的个数 \(\leq \sqrt n\),我们再用 \(\sqrt n\) 次就一定能删完所有环。换句话说,我们用 \(2\sqrt n\) 次删除可以让这个图至多剩下 \(n-1\) 条边,那么原图的边数一定不超过 \(n+2\sqrt n\)
考虑原图的一棵 \(\tt dfs\) 树,我们把非树边标记成关键点,然后建立关键点的虚树,再添加上非树边就得到了一个点数边数都是 \(O(\sqrt n)\) 级别的图。在这个图上找环就等价于在原图上找环,一开始介绍的 \(O(n^4)\) 方法是可以支持带权边找环的,直接套用它,时间复杂度 \(O(n^2)\)
#include <cstdio>
#include <vector>
#include <cstdlib>
#include <iostream>
#include <cmath>
using namespace std;
const int M = 10005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,tot=1,f[M],zz[M];vector<int> g[M];
int p[M],vis[M],dep[M],dis[M],len[M],cnt[M];
struct edge{int v,c,next;}e[M<<1];
void add(int u,int v,int c)
{
e[++tot]=edge{v,c,f[u]},f[u]=tot;
e[++tot]=edge{u,c,f[v]},f[v]=tot;
}
void dfs(int u,int fa)
{
int son=0;
dep[u]=dep[fa]+1;zz[u]=fa;
for(int v:g[u])
{
if(v==fa) continue;
if(dep[v])
{
if(dep[u]<dep[v]) continue;
if(!p[u]) p[u]=u;
if(!p[v]) p[v]=v;
add(p[u],p[v],1);
continue;
}
dfs(v,u);
if(p[v]) son=son?-1:p[v];
}
if(!p[u] && son<0) p[u]=u;
if(p[u])
{
for(int v:g[u]) if(zz[v]==u && p[v])
add(p[u],p[v],dep[p[v]]-dep[p[u]]);
}
else p[u]=son;
}
int check(int u,int nw)
{
if(!dep[u]) return 1;vis[u]=nw;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(vis[v]==nw || dep[v]>0) continue;
if(check(v,nw)) return 1;
}
return 0;
}
void work(int u,int fa)
{
if(!check(u,++k)) return ;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v,c=e[i].c,r=c+dis[u];
if(i==fa) continue;
if(!dep[v])//find root
{
if(!len[r]) len[r]=dep[u]+1;
if(len[r]!=dep[u]+1) {puts("Yes");exit(0);}
cnt[r]++;
if(cnt[r]>2*len[r]) {puts("Yes");exit(0);}
}
if(dep[v]>=0) continue;
dep[v]=dep[u]+1;dis[v]=r;
work(v,i^1);dep[v]=-1;
}
}
signed main()
{
n=read();m=read();
if(m>n+2*sqrt(n)) {puts("Yes");return 0;}
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
g[u].push_back(v);
g[v].push_back(u);
}
for(int i=1;i<=n;i++)
if(!dep[i]) dfs(i,0);
for(int i=1;i<=n;i++) dep[i]=-1;
for(int i=1;i<=n;i++) if(p[i]==i)
dep[i]=dis[i]=0,work(i,0),dep[i]=-1;
puts("No");
}