bzoj 4530: [Bjoi2014]大融合 (并查集+树链剖分+线段树)

40 篇文章 0 订阅
34 篇文章 0 订阅

4530: [Bjoi2014]大融合

Time Limit: 10 Sec   Memory Limit: 256 MB
Submit: 140   Solved: 83
[ Submit][ Status][ Discuss]

Description

小强要在N个孤立的星球上建立起一套通信系统。这套通信系统就是连接N个点的一个树。
这个树的边是一条一条添加上去的。在某个时刻,一条边的负载就是它所在的当前能够
联通的树上路过它的简单路径的数量。
例如,在上图中,现在一共有了5条边。其中,(3,8)这条边的负载是6,因
为有六条简单路径2-3-8,2-3-8-7,3-8,3-8-7,4-3-8,4-3-8-7路过了(3,8)。
现在,你的任务就是随着边的添加,动态的回答小强对于某些边的负载的
询问。

Input

第一行包含两个整数N,Q,表示星球的数量和操作的数量。星球从1开始编号。
接下来的Q行,每行是如下两种格式之一:
A x y 表示在x和y之间连一条边。保证之前x和y是不联通的。
Q x y 表示询问(x,y)这条边上的负载。保证x和y之间有一条边。
1≤N,Q≤100000

Output

对每个查询操作,输出被查询的边的负载。

Sample Input

8 6
A 2 3
A 3 4
A 3 8
A 8 7
A 6 5
Q 3 8

Sample Output

6

HINT

Source

[ Submit][ Status][ Discuss]

题解:并查集+树链剖分+线段树

要求经过一条边的简单路径的数量,答案就是两个点不通过该边所能联通的点数的乘积。

那么如何维护呢?我们先将所有的边读进来然后建树,将所有散的树枝都连接到1节点上,然后进行树链剖分。初始时所有点的子树的size都是1,如果连接一条边,就将深度较浅的点到根路径上最近的断开的点之间的区间加上深度较深的点的size。用并查集维护联通块中最浅的点。查询答案的时候用(连通块的size-较深点的size)*较深点的size即可。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 100003
#define LL long long
using namespace std;
int n,m,tr[N*4],delta[N*4];
int point[N*2],next[N*2],v[N*2],l[N],r[N],deep[N];
int belong[N],cnt,sz,tot,pos[N],fa[N],top[N],size[N],son[N];
struct data
{
	int x,y,opt;
}a[N];
void add(int x,int y)
{
	//cout<<x<<" "<<y<<endl;
	tot++; next[tot]=point[x]; point[x]=tot; v[tot]=y;
	tot++; next[tot]=point[y]; point[y]=tot; v[tot]=x;
}
void build(int x,int f)
{
	size[x]=1; fa[x]=f;
	for (int i=point[x];i;i=next[i])
	 if(v[i]!=f)
	 {
	 	deep[v[i]]=deep[x]+1;
	 	build(v[i],x);
	 	size[x]+=size[v[i]];
	 	if (size[son[x]]<size[v[i]])  son[x]=v[i];
	 }
}
void dfs(int x,int chain)
{
	pos[x]=++sz; belong[x]=chain;
	l[x]=r[x]=sz;
	if (!son[x]) return;
	dfs(son[x],chain);
	for (int i=point[x];i;i=next[i])
	 if (son[x]!=v[i]&&v[i]!=fa[x])
	  dfs(v[i],v[i]);
	r[x]=sz;
}
void pushdown(int now,int l,int r)
{
	int mid=(l+r)/2;
	if (delta[now])
	 {
	 	tr[now<<1]+=delta[now];
	 	tr[now<<1|1]+=delta[now];
	 	delta[now<<1]+=delta[now]; delta[now<<1|1]+=delta[now];
	 	delta[now]=0;
	 }
}
void buildtree(int now,int l,int r)
{
	if (l==r)
	 {
	 	tr[now]=1;
	 	return;
	 }
	int mid=(l+r)/2;
    buildtree(now<<1,l,mid);
    buildtree(now<<1|1,mid+1,r);
}
void qjchange(int now,int l,int r,int ll,int rr,int x)
{
	if (ll<=l&&r<=rr)
	{
		delta[now]+=x;
		tr[now]+=x;
		return;
	}
	int mid=(l+r)/2;
	pushdown(now,l,r);
	if (ll<=mid) qjchange(now<<1,l,mid,ll,rr,x);
	if (rr>mid) qjchange(now<<1|1,mid+1,r,ll,rr,x);
}
int pointval(int now,int l,int r,int x)
{
	if (l==r) return tr[now];
	int mid=(l+r)/2;
	pushdown(now,l,r);
	if (x<=mid) return pointval(now<<1,l,mid,x);
	else return pointval(now<<1|1,mid+1,r,x);
}
void solve(int x,int y,int z)
{
	while (belong[x]!=belong[y])
	{
		if (deep[belong[x]]<deep[belong[y]])  swap(x,y);
		qjchange(1,1,n,pos[belong[x]],pos[x],z);
		x=fa[belong[x]];
	}
    if (deep[x]>deep[y])  swap(x,y);
    qjchange(1,1,n,pos[x],pos[y],z);
}
int find(int x)
{
	if (top[x]==x) return x;
	top[x]=find(top[x]);
	return top[x];
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++) top[i]=i;
	for (int i=1;i<=m;i++)
	 {
	 	char s[10]; scanf("%s",s);
	 	if (s[0]=='A') {
	 		a[i].opt=1;
	 		scanf("%d%d",&a[i].x,&a[i].y);
	 		add(a[i].x,a[i].y);
	 		int r1=find(a[i].x); int r2=find(a[i].y);
	 		if (r1!=r2)
	 		 top[r2]=r1;
		 }
		else
		{
			a[i].opt=0;
			scanf("%d%d",&a[i].x,&a[i].y);
		}
	 }
	for (int i=1;i<=n;i++)
	 {
	 	int r1=find(i);  int r2=find(1);
	 	if (r1!=r2)
	 	 {
	 	 	top[r2]=r1;
	 	 	add(1,i);
		  }
	 }
	deep[1]=1;
	build(1,0); dfs(1,1);
	buildtree(1,1,n);
	//for (int i=1;i<=n;i++)  cout<<pos[i]<<" ";
	//cout<<endl;
	for (int i=1;i<=n;i++) top[i]=i;
	for (int i=1;i<=m;i++)
	{
		if (!a[i].opt)
		 {
		 	if (deep[a[i].x]>deep[a[i].y])  swap(a[i].x,a[i].y);
		 	int t=find(a[i].x);
		 	int x=pointval(1,1,n,pos[t]);
		 	int y=pointval(1,1,n,pos[a[i].y]);
		 	x-=y; 
		 	printf("%lld\n",(LL)x*(LL)y);
		 }
		else
		{
			if (deep[a[i].x]>deep[a[i].y]) swap(a[i].x,a[i].y);
			int k=find(a[i].x); int t=pointval(1,1,n,pos[a[i].y]);
			solve(a[i].x,k,t);
			int k1=find(a[i].y);
			top[k1]=k;
		}
	}
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值