loj#2788-「CEOI2015 Day1」管道【树上差分】
正题
题目大意
给出\(n\)个点\(m\)条边的一张图,求它的所有割边。
\(1\leq n\leq 10^5,1\leq m\leq 6\times 10^6\),内存限制16MB
解题思路
我们存不下所有的边,但是\(n\)很小。一个朴素的想法是我们搞出一棵生成树来,然后对于非树边\((x,y)\)就相当于把\(x\)到\(y\)路径上的边都标记成非割边,然后剩下的就是割边了。
但是我们不能离线建生成树,因为我们存不下所有的边,考虑一下别的方向的优化。我们会发现对于非树边来说,如果这一条非树边能被其他非树边完全覆盖,那么说明这条边就没有用,所以我们对于非树边来说也只需要保留一棵最小生成树即可。
然后至于标记方面用树上差分来处理就好了。
时间复杂度:\(O(m+n\log n)\)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cctype>
using namespace std;
const int N=1e5+10;
int n,m,fa[N],Fa[N],c[N],dep[N],f[N][18];
vector<int> G[N];bool v[N];
vector<pair<int,int> >e;
int find(int x)
{return (fa[x]==x)?(x):(fa[x]=find(fa[x]));}
int Find(int x)
{return (Fa[x]==x)?(x):(Fa[x]=Find(Fa[x]));}
int read(){
int x=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
while(isdigit(c)){x=(x<<1)+(x<<3)+c-'0';c=getchar();}
return x*f;
}
void dfs(int x,int fa){
dep[x]=dep[fa]+1;v[x]=1;
for(int i=0;i<G[x].size();i++){
int y=G[x][i];
if(y==fa)continue;
dfs(y,x);f[y][0]=x;
}
return;
}
void calc(int x,int fa){
v[x]=1;
for(int i=0;i<G[x].size();i++){
int y=G[x][i];
if(y==fa)continue;
calc(y,x);c[x]+=c[y];
}
if(!c[x]&&fa)printf("%d %d\n",x,fa);
return;
}
int LCA(int x,int y){
if(dep[x]>dep[y])swap(x,y);
for(int i=17;i>=0;i--)
if(dep[f[y][i]]>=dep[x])y=f[y][i];
if(x==y)return x;
for(int i=17;i>=0;i--)
if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
return f[x][0];
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)fa[i]=Fa[i]=i;
for(int i=1;i<=m;i++){
int x=read(),y=read();
if(find(x)!=find(y)){
fa[find(x)]=find(y);
G[x].push_back(y);
G[y].push_back(x);
}
else if(Find(x)!=Find(y)){
Fa[Find(x)]=Find(y);
e.push_back(make_pair(x,y));
}
}
for(int i=1;i<=n;i++)
if(!v[i])dfs(i,0);
memset(v,0,sizeof(v));
for(int j=1;j<18;j++)
for(int i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
for(int i=0;i<e.size();i++){
int x=e[i].first,y=e[i].second;
c[x]++;c[y]++;c[LCA(x,y)]-=2;
}
for(int i=1;i<=n;i++)
if(!v[i])calc(i,0);
return 0;
}