Loj#3077-「2019 集訓隊互測 Day 4」絕目編詩【結論,虛樹,鴿籠原理】
正題
題目大意
給出\(n\)個點\(m\)條邊的一張簡單無向圖,求是否存在兩個長度相等的簡單環。
\(1\leq n\leq 10^4,1\leq m\leq 10^6\)
解題思路
先考慮一個暴力的做法,我們暴力搜尋圖上的所有環,記\(c_i\)表示長度為\(i\)的環的個數。
那麼注意到一個長度為\(x\)的環,我們會重複統計\(2x\)次(一輪和翻轉後的一輪),所以如果\(c_x>2x\)那麼就可以輸出\(Yes\)了。
根據鴿籠原理,我們能注意到圖上的環的總數不能超過\(n-2\)(長度為\(3\sim n\)的各一個),否則答案一定是\(Yes\)
然後考慮如果我們走的每一步都能保證往後是能搜出至少一個環的話,那麼一個環最多被統計\(2x\)次,也就是要搜\(2x^2\)個點,那麼如果答案是\(No\)我們就最多隻需要搜\(\sum_{i=1}^n2i^2\)也就是\(O(n^3)\)級別次。
那麼如何保證我們走的每個點一定能搜出環,很簡單,假設我們的環從\(s\)出發,我們走到一個點是時暴力\(O(n)\)地判斷它是否能不經過目前重複的走到\(s\),如果能那麼它搜下去至少會有一個新的環。
這樣我們就做到\(O(n^4)\)的複雜度了。
然後是一個神仙優化
我們假設我們在圖上刪除\(\sqrt n\)條邊,那麼每條邊被刪除的概率就是\(\frac{1}{\sqrt n}\),假設圖恰好長度為\(3\sim n\)的環各有一個,那麼一個長度為\(x\)的環沒被刪除的概率就是\((1-\frac{1}{\sqrt n})^x\),同理那最後圖上剩下的環的期望個數就是
\[\sum_{i=3}^n\left(1-\frac{1}{\sqrt n}\right)^i<\frac{1}{1-\left(1-\frac{1}{\sqrt n}\right)}=\sqrt n \]所以刪除\(\sqrt n\)
然後我們指定刪一條邊肯定能刪掉一個環,所以我們只需要刪除\(2\sqrt n\)條邊就可以使得圖上不存在任何一個環,換句話說我只需要刪除\(2\sqrt n\)條邊就可以讓這張圖的邊數不超過\(n-1\),所以這張圖的邊數不超過\(n-1+2\sqrt n\)。
所以再換句話說,我們就得到了最重要的結論,如果圖的邊數大於等於\(n+2\sqrt n\)那麼答案肯定是\(Yes\)。
所以我們只需要考慮怎麼處理\(n+2\sqrt n\)的情況,我們先找出一棵生成樹,然後對於所有的非樹邊的端點建出一棵虛樹,然後連線上非樹邊,這樣我們就得到一張邊數和點數都是\(\sqrt n\)級別的圖。
不難發現我們上面那種情況也是能處理帶權圖的(需要注意的是一個長度為\(x\)的環出現的次數不是\(2x\)了,我們需要記錄這個環在虛樹上的邊數\(s\),如果存在兩種不同的邊數肯定是兩個不同的環,否則就判斷出現次數\(>2s\)),所以我們的複雜度就到了快速的\(O(n^2)\)了。
因為跑不滿並且這是LOJ所以能過。
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
const int N=1e4+10;
struct node{
int to,next,w;
}a[N<<1];
int n,m,tot,cnt,ls[N],dep[N],pos[N];
int las[N],vis[N],dis[N],len[N],c[N];
vector<int> G[N];
void addl(int x,int y,int w){
a[++tot].to=y;a[tot].next=ls[x];ls[x]=tot;a[tot].w=w;
a[++tot].to=x;a[tot].next=ls[y];ls[y]=tot;a[tot].w=w;
return;
}
void dfs(int x,int fa){
dep[x]=dep[fa]+1;las[x]=fa;int num=0;
for(int i=0;i<G[x].size();i++){
int y=G[x][i];
if(y==fa)continue;
if(dep[y]){
if(dep[y]>dep[x])continue;
if(!pos[x])pos[x]=x;
if(!pos[y])pos[y]=y;
addl(pos[x],pos[y],1);
}
else{
dfs(y,x);
if(pos[y])num=num?-1:pos[y];
}
}
if(!pos[x]&&num<0)pos[x]=x;
if(pos[x]){
for(int i=0;i<G[x].size();i++){
int y=G[x][i];
if(y==fa)continue;
if(pos[y]&&las[y]==x)
addl(pos[x],pos[y],dep[pos[y]]-dep[pos[x]]);
}
}
else pos[x]=num;
return;
}
bool calc(int x,int fa,int t){
if(!dep[x])return 1;vis[x]=t;
for(int i=ls[x];i;i=a[i].next){
int y=a[i].to;
if(i==fa||dep[y]>0||vis[y]==t)continue;
bool flag=calc(y,i^1,t);
if(flag)return 1;
}
return 0;
}
void solve(int x,int fa){
if(!calc(x,fa,++cnt))return;
for(int i=ls[x];i;i=a[i].next){
int y=a[i].to,r=dis[x]+a[i].w;
if(i==fa)continue;
if(!dep[y]){
if(!len[r])len[r]=dep[x]+1;
if(len[r]!=dep[x]+1){puts("Yes");exit(0);}
c[r]++;
if(c[r]>len[r]*2){puts("Yes");exit(0);}
}
if(dep[y]>=0)continue;
dep[y]=dep[x]+1;dis[y]=dis[x]+a[i].w;
solve(y,i^1);dep[y]=-1;
}
return;
}
int main()
{
// freopen("4-01.in","r",stdin);
scanf("%d%d",&n,&m);tot=1;
if(m>n+sqrt(n)*2.0)return puts("Yes")&0;
for(int i=1,x,y;i<=m;i++){
scanf("%d%d",&x,&y);
G[x].push_back(y);
G[y].push_back(x);
}
for(int i=1;i<=n;i++)
if(!dep[i])dfs(i,0);
memset(dep,-1,sizeof(dep));
for(int i=1;i<=n;i++)
if(pos[i]==i)
dep[i]=0,dis[i]=0,solve(i,0),dep[i]=-1;
puts("No");
return 0;
}