bzoj3123【SDOI2013】森林

3123: [Sdoi2013]森林

Time Limit: 20 Sec   Memory Limit: 512 MB
Submit: 1905   Solved: 587
[ Submit][ Status][ Discuss]

Description

bzoj3123【SDOI2013】森林_第1张图片

Input

第一行包含一个正整数testcase,表示当前测试数据的测试点编号。保证1≤testcase≤20。 
第二行包含三个整数N,M,T,分别表示节点数、初始边数、操作数。第三行包含N个非负整数表示 N个节点上的权值。 
 接下来 M行,每行包含两个整数x和 y,表示初始的时候,点x和点y 之间有一条无向边, 接下来 T行,每行描述一个操作,格式为“Q x y k”或者“L x y ”,其含义见题目描述部分。

Output

对于每一个第一类操作,输出一个非负整数表示答案。 
 
 

Sample Input

1
8 4 8
1 1 2 2 3 3 4 4
4 7
1 8
2 4
2 1
Q 8 7 3 Q 3 5 1
Q 10 0 0
L 5 4
L 3 2 L 0 7
Q 9 2 5 Q 6 1 6

Sample Output

2
2
1
4
2

HINT



对于第一个操作 Q 8 7 3,此时 lastans=0,所以真实操作为Q 8^0 7^0 3^0,也即Q 8 7 3。点8到点7的路径上一共有5个点,其权值为4 1 1 2 4。这些权值中,第三小的为 2,输出 2,lastans变为2。对于第二个操作 Q 3 5 1 ,此时lastans=2,所以真实操作为Q 3^2 5^2 1^2 ,也即Q 1 7 3。点1到点7的路径上一共有4个点,其权值为 1 1 2 4 。这些权值中,第三小的为2,输出2,lastans变为 2。之后的操作类似。 

 

bzoj3123【SDOI2013】森林_第2张图片




可持久化线段树+启发式合并

如果只有查询操作,每次询问找到两个点的LCA,然后用可持久化线段树加加减减查询第k大即可。

对于连接操作我们要用启发式合并,把小的集合合并到大的上面,并把小的暴力重建主席树。

这样时间和空间复杂度就是O(n*logn^2)。

这道题我调了好久好久...后来要了数据才发现即使testcase>1也只有一组数据hhhh...数据坑爹怪我吗

然而把这个改了还是RE,原来是加边的cnt和主席树的cnt搞混了...啊啊啊欲哭无泪




#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#define F(i,j,n) for(int i=j;i<=n;i++)
#define D(i,j,n) for(int i=j;i>=n;i--)
#define ll long long
#define N 100005
#define M 40000000
#define inf 1000000000
using namespace std;
int n,m,t,cnt,cnt_e,ans;
int a[N],head[N],rt[N],f[N],sum[N],dep[N],fa[N][20];
int sz[M],ls[M],rs[M];
struct edge_type{int next,to;}e[N*2];
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
inline void add_edge(int x,int y)
{
	e[++cnt_e]=(edge_type){head[x],y};head[x]=cnt_e;
	e[++cnt_e]=(edge_type){head[y],x};head[y]=cnt_e;
}
int find(int x)
{
	return f[x]==x?x:f[x]=find(f[x]);
}
void merge(int x,int y)
{
	int fx=find(x),fy=find(y);
	f[fx]=fy;sum[fy]+=sum[fx];
}
int lca(int x,int y)
{
	if (dep[x]<dep[y]) swap(x,y);
	D(i,19,0) if (dep[fa[x][i]]>=dep[y]) x=fa[x][i];
	if (x==y) return x;
	D(i,19,0) if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
	return fa[x][0];
}
void insert(int x,int &y,int l,int r,int val)
{
	if (!y) y=++cnt;
	sz[y]=sz[x]+1;
	if (l==r) return;
	int mid=(l+r)>>1;
	if (val<=mid) rs[y]=rs[x],insert(ls[x],ls[y],l,mid,val);
	else ls[y]=ls[x],insert(rs[x],rs[y],mid+1,r,val);
}
int getkth(int x,int y,int z,int w,int l,int r,int k)
{
	if (l==r) return l;
	int mid=(l+r)>>1;
	int tmp=sz[ls[x]]+sz[ls[y]]-sz[ls[z]]-sz[ls[w]];
	if (k<=tmp) return getkth(ls[x],ls[y],ls[z],ls[w],l,mid,k);
	else return getkth(rs[x],rs[y],rs[z],rs[w],mid+1,r,k-tmp);
}
void dfs(int x)
{
	F(i,1,19) fa[x][i]=fa[fa[x][i-1]][i-1];
	dep[x]=dep[fa[x][0]]+1;
	rt[x]=0;
	insert(rt[fa[x][0]],rt[x],0,inf,a[x]);
	for(int i=head[x];i;i=e[i].next) if (e[i].to!=fa[x][0])
	{
		fa[e[i].to][0]=x;
		dfs(e[i].to);
	}
}
int main()
{
	int hhh=read();
	n=read();m=read();t=read();
	F(i,1,n) a[i]=read();
	F(i,1,n) f[i]=i,sum[i]=1;
	F(i,1,m)
	{
		int x=read(),y=read();
		add_edge(x,y);merge(x,y);
	}
	F(i,1,n) if (!fa[i][0]) dfs(i);
	F(i,1,t)
	{
		char opt=getchar();while (opt!='Q'&&opt!='L') opt=getchar();
		if (opt=='Q')
		{
			int x=read()^ans,y=read()^ans,k=read()^ans,lc=lca(x,y);
			ans=getkth(rt[x],rt[y],rt[lc],rt[fa[lc][0]],0,inf,k);
			printf("%d\n",ans);
		}
		else
		{
			int x=read()^ans,y=read()^ans,fx=find(x),fy=find(y);
			if (sum[fx]>sum[fy]) swap(x,y);
			add_edge(x,y);merge(x,y);
			fa[x][0]=y;dfs(x);
		}
	}
	return 0;
}


你可能感兴趣的:(bzoj,可持久化线段树,启发式合并)