您的位置:首页 > 其它

JZOJ 3766. 【BJOI2014】大融合

2017-06-24 21:48 441 查看

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之间有一条边。

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

Data Constraint

对于40%的数据,N,Q≤1000

对于100%的数据,1≤N,Q≤100000

Solution

这题的思路很巧妙。考虑树链剖分和并查集。

先把边都连上,形成一些森林,处理树链剖分的有关信息。

接着再逐个连边,把深度高的点设为x,深度低的点设为y,y所在的链链顶为z。

再设x的子树大小为m1,z的子树大小为m2,有如下情况:

①:连边操作,从y到z的子树大小全部加上x的子树大小(用线段树区间加实现)

②:查询操作,则答案即为m1*(m2-m1)。

Code

#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100001;
struct data
{
bool z;
int x,y;
}a
;
struct segment
{
int l,r,sum,c;
}g[N<<2];
int tot,num;
int first
,next[N<<1],en[N<<1];
int fa
,size
,dep
;
int top
,son
,tree
,pre
;
int f
,t
,h
;
inline int read()
{
int X=0,w=1; char ch=0;
while(ch<'0' || ch>'9') {if(ch=='-') w=-1;ch=getchar();}
while(ch>='0' && ch<='9') X=(X<<3)+(X<<1)+ch-'0',ch=getchar();
return X*w;
}
inline void insert(int x,int y)
{
next[++tot]=first[x];
first[x]=tot;
en[tot]=y;
}
inline int get(int x)
{
if(f[x]==x) return x;
return f[x]=get(f[x]);
}
inline void dfs1(int x)
{
dep[x]=dep[fa[x]]+1;
size[x]=1;
for(int i=first[x];i;i=next[i])
if(en[i]!=fa[x])
{
fa[en[i]]=x;
dfs1(en[i]);
size[x]+=size[en[i]];
if(!son[x] || size[son[x]]<size[en[i]]) son[x]=en[i];
}
}
inline void dfs2(int x,int y)
{
top[pre[tree[x]=++num]=x]=y;
if(!son[x]) return;
dfs2(son[x],y);
for(int i=first[x];i;i=next[i])
if(en[i]!=fa[x] && en[i]!=son[x]) dfs2(en[i],en[i]);
}
inline void update(int v)
{
int ls=v<<1,rs=ls|1;
if(g[v].c)
{
g[ls].sum+=g[v].c;
g[ls].c+=g[v].c;
g[rs].sum+=g[v].c;
g[rs].c+=g[v].c;
g[v].c=0;
}
}
inline void make(int v,int l,int r)
{
g[v].l=l,g[v].r=r;
if(l==r)
{
g[v].sum=1;
return;
}
int mid=(l+r)>>1;
make(v<<1,l,mid);
make(v<<1|1,mid+1,r);
g[v].sum=g[v<<1].sum+g[v<<1|1].sum+1;
}
inline void change(int v,int x,int y,int z)
{
if(g[v].l==x && g[v].r==y)
{
g[v].sum+=z;
g[v].c+=z;
return;
}
update(v);
int mid=(g[v].l+g[v].r)>>1;
if(y<=mid) change(v<<1,x,y,z); else
if(x>mid) change(v<<1|1,x,y,z); else
{
change(v<<1,x,mid,z);
change(v<<1|1,mid+1,y,z);
}
}
inline int find(int v,int x)
{
if(g[v].l==g[v].r) return g[v].sum;
update(v);
int mid=(g[v].l+g[v].r)>>1;
if(x<=mid) return find(v<<1,x);
return find(v<<1|1,x);
}
int main()
{
int n=read(),q=read();
for(int i=1;i<=q;i++)
{
char ch=getchar();
while(ch!='A' && ch!='Q') ch=getchar();
a[i].x=read(),a[i].y=read();
if(a[i].z=ch=='A')
{
insert(a[i].x,a[i].y);
insert(a[i].y,a[i].x);
}
}
for(int i=1;i<=n;i++)
if(!size[i])
{
dfs1(i);
dfs2(i,i);
}
make(1,1,n);
for(int i=1;i<=n;i++) h[t[f[i]=i]=i]=1;
for(int i=1;i<=q;i++)
{
int x=a[i].x,y=a[i].y;
if(dep[x]<dep[y]) swap(x,y);
int f1=get(x),f2=get(y);
if(a[i].z)
{
int n1=y,n2=t[f2];
int m1=top[n1],m2=top[n2];
if(dep[m1]<dep[t[f2]]) m1=t[f2];
if(dep[m2]<dep[t[f2]]) m2=t[f2];
while(m1!=m2)
{
if(dep[m1]<dep[m2]) swap(m1,m2),swap(n1,n2);
change(1,tree[m1],tree[n1],h[f1]);
n1=fa[m1],m1=top[n1];
if(dep[m1]<dep[t[f2]]) m1=t[f2];
}
if(dep[n1]>dep[n2]) swap(n1,n2);
change(1,tree[n1],tree[n2],h[f1]);
h[f2]+=h[f1];
if(dep[t[f1]]<dep[t[f2]]) t[f2]=t[f1];
f[f1]=f2;
}else
{
int m1=find(1,tree[x]),m2=find(1,tree[t[f2]])-m1;
printf("%lld\n",(long long)m1*m2);
}
}
return 0;
}


方法二

显然这道题也可以用可维护子树信息的LCT。

那么 LCT 如何维护子树信息呢?(以维护子树大小为例)

我们可以多开一个数组记录一个点虚树上的大小信息,合并时直接并到该点上。

我们发现只有当 Access 或 Link 操作时会影响到信息记录(其他操作都不会影响)。

当 Access 时,只需将虚树信息加上原右儿子的 Size ,再减去新右儿子的 Size 即可。

而当 Link 时,相当于 xx 向 yy 添一条虚边,

于是除了 makeroot(x) 外,再执行一遍 makeroot(y),(消除修改 yy 对其子树各节点信息的影响)

最后再将 yy 虚树信息加上 xx 的 Size 即可。

时间复杂度 O(N log N)O(N log N) 。

Code2

#include<cstdio>
#include<algorithm>
#include<cctype>
using namespace std;
const int N=1e5+5;
int top;
int fa
,size
,s
[2],g
,st
;
bool rev
;
inline int read()
{
int X=0,w=0; char ch=0;
while(!isdigit(ch)) w|=ch=='-',ch=getchar();
while(isdigit(ch)) X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
return w?-X:X;
}
inline bool pd(int x)
{
return x==s[fa[x]][1];
}
inline bool isroot(int x)
{
return x^s[fa[x]][0] && x^s[fa[x]][1];
}
inline void reverse(int x)
{
if(x) swap(s[x][0],s[x][1]),rev[x]^=1;
}
inline void update(int x)
{
size[x]=size[s[x][0]]+size[s[x][1]]+1+g[x];
}
inline void down(int x)
{
if(rev[x])
{
reverse(s[x][0]),reverse(s[x][1]);
rev[x]=false;
}
}
inline void rotate(int x)
{
int y=fa[x],w=pd(x);
if(s[y][w]=s[x][w^1]) fa[s[y][w]]=y;
if((fa[x]=fa[y]) && !isroot(y)) s[fa[y]][pd(y)]=x;
s[fa[y]=x][w^1]=y;
update(y);
}
inline void splay(int x)
{
for(int y=st[top=1]=x;!isroot(y);y=fa[y]) st[++top]=fa[y];
while(top) down(st[top--]);
for(int y;!isroot(x);rotate(x))
if(!isroot(y=fa[x])) rotate(pd(x)==pd(y)?y:x);
update(x);
}
inline void access(int x)
{
for(int y=0;x;x=fa[y=x])
{
splay(x);
g[x]+=size[s[x][1]];
g[x]-=size[s[x][1]=y];
update(x);
}
}
inline void mkroot(int x)
{
access(x),splay(x),reverse(x);
}
inline void link(int x,int y)
{
mkroot(x),mkroot(y);
g[fa[x]=y]+=size[x];
update(y);
}
int main()
{
int n=read(),m=read();
for(int i=1;i<=n;i++) size[i]=1;
while(m--)
{
char ch=getchar();
int x=read(),y=read();
if(ch=='A') link(x,y); else
{
mkroot(x),access(y),splay(y);
printf("%lld\n",(long long)size[x]*(size[y]-size[x]));
}
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: