【树链剖分】BZOJ2896 桥

【题目】
原题地址
给定一幅 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 n3×104,m105,Q4×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;
}

【总结】
其实这个问题转化也挺厉害的吧。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值