【题目】
原题地址
给定一幅
n
n
n个点
m
m
m条边的无向图,两种操作
- 删除一条边
- 询问两点间有多少条边是桥边
n ≤ 3 × 1 0 4 , m ≤ 1 0 5 , Q ≤ 4 × 1 0 5 n\leq 3\times 10^4,m\leq 10^5,Q\leq 4\times 10^5 n≤3×104,m≤105,Q≤4×105
【解题思路】
首先考虑暴力,我们每次删边后,将图的边双缩起来,那么两点间桥的数量就是这两点所在边双在树上的距离。
瓶颈主要在重构,我们需要考虑一种做法使得我们不需要重构树,而是在一棵树上进行操作,并使用数据结构进行维护。
一种可行的思路是,我们倒序处理这个问题,这样删边就变成了加边。
如果我们最终操作出来是一棵树,那么我们加边相当于将树上一段路径边权修改为
0
0
0,询问就是两点间边权和,这个用树剖可以轻松解决。
现在我们最终处理出来不一定是一棵树,然而并没有什么关系,我们强行将它处理成一棵树,然后再加回去就可以了(虽然常数可能有点大?)。
复杂度
O
(
(
m
+
q
)
l
o
g
n
+
m
l
o
g
m
)
O((m+q)logn+mlogm)
O((m+q)logn+mlogm)(后面那个是我比较蠢的处理方法,详见代码)
【参考代码】
#include<bits/stdc++.h>
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef pair<int,int> pii;
const int N=1e5+10,M=2e5+10;
int n,m,cnt,css,ans[M];
set<int>sa[N],sd[N];
pii cg[M];
int read()
{
int ret=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=0;c=getchar();}
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return f?ret:-ret;
}
struct data
{
int op,u,v;
data(int _op=0,int _u=0,int _v=0):op(_op),u(_u),v(_v){}
}q[M];
namespace Graph
{
int tot,head[N];
struct Tway{int v,nex;}e[M<<1];
void add(int u,int v){e[++tot]=(Tway){v,head[u]};head[u]=tot;}
void addedge(int u,int v){add(u,v);add(v,u);}
};
using namespace Graph;
namespace Train
{
struct Segment
{
#define ls (x<<1)
#define rs (x<<1|1)
int w[N<<2],t[N<<2];
void pushup(int x){w[x]=w[ls]+w[rs];}
void pushdown(int x){if(t[x]){w[ls]=w[rs]=t[x]=0;t[ls]=t[rs]=1;}}
void build(int x,int l,int r)
{
if(l==r){w[x]=1;t[x]=0;return;}
int mid=(l+r)>>1;
build(ls,l,mid);build(rs,mid+1,r);
pushup(x);
}
void update(int x,int l,int r,int L,int R)
{
if(L<=l && r<=R){w[x]=0;t[x]=1;return;}
pushdown(x);
int mid=(l+r)>>1;
if(L<=mid) update(ls,l,mid,L,R);
if(R>mid) update(rs,mid+1,r,L,R);
pushup(x);
}
int query(int x,int l,int r,int L,int R)
{
if(L<=l && r<=R) return w[x];
pushdown(x);
int mid=(l+r)>>1,res=0;
if(L<=mid) res+=query(ls,l,mid,L,R);
if(R>mid) res+=query(rs,mid+1,r,L,R);
pushup(x); return res;
}
}tr;
int ind,son[N],top[N];
int siz[N],fa[N],dep[N],pos[N];
void dfs1(int x)
{
siz[x]=1;
for(int i=head[x];i;i=e[i].nex)
{
int v=e[i].v;
if(v==fa[x]) continue;
fa[v]=x;dep[v]=dep[x]+1;
dfs1(v);siz[x]+=siz[v];
if(siz[v]>siz[son[x]]) son[x]=v;
}
}
void dfs2(int x,int tp)
{
pos[x]=++ind;top[x]=tp;
if(son[x]) dfs2(son[x],tp);
for(int i=head[x];i;i=e[i].nex)
{
int v=e[i].v;
if(v^fa[x] && v^son[x]) dfs2(v,v);
}
}
void update(int x,int y)
{
while(top[x]^top[y])
{
if(dep[top[x]]<dep[top[y]]) swap(x,y);
tr.update(1,1,n,pos[top[x]],pos[x]);
x=fa[top[x]];
}
if(dep[x]<dep[y]) swap(x,y);
if(x^y) tr.update(1,1,n,pos[son[y]],pos[x]);
}
int query(int x,int y)
{
int res=0;
while(top[x]^top[y])
{
if(dep[top[x]]<dep[top[y]]) swap(x,y);
res+=tr.query(1,1,n,pos[top[x]],pos[x]);
x=fa[top[x]];
}
if(dep[x]<dep[y]) swap(x,y);
if(x^y) res+=tr.query(1,1,n,pos[son[y]],pos[x]);
return res;
}
void init()
{
dfs1(1);dfs2(1,1);
tr.build(1,1,n);
}
};
namespace DSU
{
int fa[N];
void init(){for(int i=1;i<=n;++i) fa[i]=i;}
int findf(int x){return fa[x]==x?x:fa[x]=findf(fa[x]);}
void merge(int x,int y)
{
int fx=findf(x),fy=findf(y);
if(fx==fy) cg[++css]=mkp(x,y);
else fa[fx]=fy,addedge(x,y);
}
};
int main()
{
#ifndef ONLINE_JUDGE
freopen("BZOJ2896.in","r",stdin);
freopen("BZOJ2896.out","w",stdout);
#endif
n=read();m=read();
for(int i=1,u,v;i<=m;++i)
{
u=read();v=read();if(u>v)swap(u,v);
sa[u].insert(v);
}
for(int op=read(),u,v;~op;op=read())
{
u=read();v=read();if(u>v)swap(u,v);
q[++cnt]=data(op,u,v);
if(!op) sd[u].insert(v);
}
DSU::init();
for(int i=1;i<=n;++i)
{
for(set<int>::iterator it=sa[i].begin();it!=sa[i].end();++it)
{
int u=i,v=*it;
if(sd[u].count(v)) continue;
DSU::merge(u,v);
}
}
Train::init();
for(int i=1;i<=css;++i) Train::update(cg[i].fi,cg[i].se);
for(int i=cnt;i;--i)
{
if(q[i].op) ans[i]=Train::query(q[i].u,q[i].v);
else Train::update(q[i].u,q[i].v);
}
for(int i=1;i<=cnt;++i) if(q[i].op) printf("%d\n",ans[i]);
return 0;
}
【总结】
其实这个问题转化也挺厉害的吧。。。