这里放传送门
这题猛一看跟线段树啊树链剖分什么的一点儿关系也没有啊好吧?一开始还以为它是那种像某个叫做【堵塞的交通】的题目那样是个线段树维护连通性啊啥的鬼畜玩意儿叻。。。可是这道题的图里面一个很重要的性质决定了它并不是第二个【堵塞的交通】。。。就是如果图上出现一个环,那么环上的边都不可能成为关键边。那么这就引出了一个好的想法就是找出每个时刻的图上有多少个环,不在任何一个环上的那些边就是关键边。
可以看出来随着题目一直删边,关键边的数目是单调不降的。删边这种操作可能不大好处理,于是我们离线询问倒序加边,于是环的数目会一直增加。考虑一种极限情况,当这个图是一棵树的时候,所有边都是关键边。而树上一旦加入一条边成环以后,环连接的树链上的所有边都不会再是关键边了。到这里做法就比较显然了,我们对初始的图建立一棵生成树,初始边权都赋值为1,每次加入一条边的时候就把这条边连接的两个端点在树上的路径赋值为0,查询的时候就是查询路径上有多少个1,用链剖维护就可以了。
#include
#include
#include
#include
using namespace std;
int c,A,B,n,m,cnt,p[30010],next[60010],a[60010],father[30010],tot,deep[30010],fa[30010],top[30010],son[30010],size[30010];
int w[30010],t[130000],qcnt;
bool cov[130000];
struct edge{
int x,y;
bool dyd,usd;
bool operator < (const edge &a)const{return xx||(x==a.x&&yy);}
}e[100010];.//注意重载要两个关键字
mapint> rec;
struct query{
int opt,u,v,ans;
}q[40010];
void add(int x,int y){
tot++;a[tot]=y;next[tot]=p[x];p[x]=tot;
}
int find(int x){
if (x==father[x]) return x;
father[x]=find(father[x]);
return father[x];
}
void dfs(int u){
deep[u]=deep[fa[u]]+1;
son[u]=0;size[u]=1;
for (int i=p[u];i!=0;i=next[i])
if (a[i]!=fa[u]){
fa[a[i]]=u;
dfs(a[i]);
size[u]+=size[a[i]];
if (size[a[i]]>size[son[u]])
son[u]=a[i];
}
}
void dfs_again(int u,int tp){
top[u]=tp;w[u]=++cnt;
if (son[u]!=0) dfs_again(son[u],tp);
for (int i=p[u];i!=0;i=next[i])
if (a[i]!=fa[u]&&a[i]!=son[u]) dfs_again(a[i],a[i]);
}
void update(int i){t[i]=t[i<<1]+t[(i<<1)+1];}
void pushdown(int i,int l,int r){
if (cov[i]==true){
int mid=(l+r)>>1;
cov[i<<1]=cov[(i<<1)+1]=true;
t[i<<1]=t[(i<<1)+1]=0;
cov[i]=false;
}
}
void build(int i,int l,int r){
if (l==r){t[i]=(l!=1);return;}
int mid=(l+r)>>1;
build(i<<1,l,mid);
build((i<<1)+1,mid+1,r);
update(i);
}
void change(int i,int l,int r,int left,int right){
if (left<=l&&right>=r){
t[i]=0;cov[i]=true;return;
}
int mid=(l+r)>>1;
pushdown(i,l,r);
if (left<=mid) change(i<<1,l,mid,left,right);
if (right>mid) change((i<<1)+1,mid+1,r,left,right);
update(i);
}
void Clear(int x,int y){
if (x==y) return;
while (top[x]!=top[y]){
if (deep[top[x]]y]]) swap(x,y);
change(1,1,n,w[top[x]],w[x]);
x=fa[top[x]];
}
if (x==y) return;
if (deep[x]>deep[y]) swap(x,y);
change(1,1,n,w[x]+1,w[y]);
}
int ask(int i,int l,int r,int left,int right){
if (left<=l&&right>=r) return t[i];
int mid=(l+r)>>1,ans=0;
pushdown(i,l,r);
if (left<=mid) ans+=ask(i<<1,l,mid,left,right);
if (right>mid) ans+=ask((i<<1)+1,mid+1,r,left,right);
return ans;
}
int Query(int x,int y){
int ans=0;
if (x==y) return 0;
while (top[x]!=top[y]){
if (deep[top[x]]y]]) swap(x,y);
ans+=ask(1,1,n,w[top[x]],w[x]);
x=fa[top[x]];
}
if (x==y) return ans;
if (deep[x]>deep[y]) swap(x,y);
ans+=ask(1,1,n,w[x]+1,w[y]);
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++){
scanf("%d%d",&e[i].x,&e[i].y);
if (e[i].x>e[i].y) swap(e[i].x,e[i].y);
rec[e[i]]=i;
}
for (int i=1;i<=n;i++) father[i]=i;
while (scanf("%d",&c)==1){
if (c==-1) break;
scanf("%d%d",&A,&B);
if (A>B) swap(A,B);
++qcnt;q[qcnt].opt=c;q[qcnt].u=A;q[qcnt].v=B;
if (c==0){
edge z;z.x=A;z.y=B;
e[rec[z]].dyd=true;
}
}
for (int i=1;i<=m;i++)
if (e[i].dyd==false){
int r1,r2;
r1=find(e[i].x);r2=find(e[i].y);
if (r1!=r2){
father[r2]=r1;e[i].usd=true;
add(e[i].x,e[i].y);
}
}
dfs(1);dfs_again(1,1);
build(1,1,n);
for (int i=1;i<=m;i++)
if (e[i].dyd==false&&e[i].usd==false)
Clear(e[i].x,e[i].y);//如果这条边既没有毁掉也不是树边就在线段树中处理
for (int i=qcnt;i>=1;i--)
if (q[i].opt==0) Clear(q[i].u,q[i].v);
else q[i].ans=Query(q[i].u,q[i].v);
for (int i=1;i<=qcnt;i++)
if (q[i].opt==1) printf("%d\n",q[i].ans);
return 0;
}
1.因为题目询问的时候读入的是边的两个端点,所以还要把它们映射到边的编号上去。这里用了map,每次建立一个结构体扔到map里面去,查询的时候用结构体在map里面查,注意结构体里面端点要按一定顺序存储。然而这里出现了一个值得注意的问题就是map的运算符重载,因为这个映射的结构体里面有两个关键字x和y,两个都相同才能判断是同一条边,所以重载的时候也要重载两个关键字,不能只重载x<=a.x之类,要不然它会把形如 (1,1) 和 (1,2) 这两条边看成同一条边。。。
2.很多图的问题其实是可以搞一搞生成树转化为树上问题的啦