BZOJ 3123 SDOI2013 森林 可持久化线段树+倍增LCA+启发式合并

题目大意:给定一棵森林,每个点有权值,提供两种操作:

1.查询两点间路径上第k小的权值

2.将两个点之间连一条边 保证连接后仍是一座森林

可持久化线段树部分同Count On A Tree 只是这道题加了个连接操作

对于连接操作我们要用到启发式合并 就是把小的那棵树暴力重建 很简单的一个操作但是可以证明是均摊O(nlogn)的

大小我用了并查集 其实记录根就可以了

此外本题的多组数据是在逗比 记住testcase恒等于1就是了

NND我倍增LCA又写错了0.0 预处理时居然从大往小写的0.0 样例还过了0.0

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define M 80100
using namespace std;
struct Tree{
	Tree *ls,*rs;
	int num;
}*tree[M],mempool[20002000],*C=mempool;
struct abcd{
	int to,next;
}table[M<<1];
int head[M],tot;
int n,m,q,ans;
int a[M],fa[M][20],dpt[M],root[M],siz[M],stack[M],top;
int Find(int x)
{
	if(!root[x])
		siz[x]=1,root[x]=x;
	if(root[x]==x)
		return x;
	return root[x]=Find(root[x]);
}
inline void Unite(int x,int y)
{
	int fx=Find(x);
	int fy=Find(y);
	root[fy]=fx;
	siz[fx]+=siz[fy];
}
inline void Add(int x,int y)
{
	table[++tot].to=y;
	table[tot].next=head[x];
	head[x]=tot;
}
inline Tree* New_Node(Tree* _,Tree* __,int ___)
{
	C->ls=_;
	C->rs=__;
	C->num=___;
	return C++;
}
Tree* Build_Tree(Tree *p,int x,int y,int num)
{
	int mid=x+y>>1;
	if(x==y)
		return New_Node(0x0,0x0,p->num+1);
	if(num<=mid)
		return New_Node(Build_Tree(p->ls,x,mid,num),p->rs,p->num+1);
	else
		return New_Node(p->ls,Build_Tree(p->rs,mid+1,y,num),p->num+1);
}
int Get_Kth(Tree *p1,Tree *p2,Tree *p3,Tree *p4,int x,int y,int k)
{
	int mid=x+y>>1;
	if(x==y)
		return mid;
	int temp = p1->ls->num + p2->ls->num - p3->ls->num - p4->ls->num;
	if(k<=temp)
		return Get_Kth(p1->ls,p2->ls,p3->ls,p4->ls,x,mid,k);
	else
		return Get_Kth(p1->rs,p2->rs,p3->rs,p4->rs,mid+1,y,k-temp);
}
void DFS1(int x)
{
	int i;
	dpt[x]=dpt[fa[x][0]]+1;
	tree[x]=Build_Tree(tree[fa[x][0]],0,1000000000,a[x]);
	for(i=head[x];i;i=table[i].next)
	{
		if(table[i].to==fa[x][0])
			continue;
		fa[table[i].to][0]=x;
		DFS1(table[i].to);
	}
}
void DFS2(int x,int from)
{
	int i;
	fa[x][0]=from;
	dpt[x]=dpt[fa[x][0]]+1;
	tree[x]=Build_Tree(tree[fa[x][0]],0,1000000000,a[x]);
	stack[++top]=x;
	for(i=head[x];i;i=table[i].next)
	{
		if(table[i].to==from)
			continue;
		DFS2(table[i].to,x);
	}
}
inline int LCA(int x,int y)
{
	int j;
	if(dpt[x]<dpt[y])
		swap(x,y);
	for(j=19;~j;j--)
		if(dpt[fa[x][j]]>=dpt[y])
			x=fa[x][j];
	if(x==y)
		return x;
	for(j=19;~j;j--)
		if(fa[x][j]!=fa[y][j])
			x=fa[x][j],y=fa[y][j];
	return fa[x][0];
}
int main()
{
	int T,i,j,x,y,k;
	char p[10];
	cin>>T;
	cin>>n>>m>>q;
	for(i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for(i=1;i<=m;i++)
		scanf("%d%d",&x,&y),Add(x,y),Add(y,x),Unite(x,y);
	tree[0]=New_Node(C,C,0);
	for(i=1;i<=n;i++)
		if(!fa[i][0])
			DFS1(i);
	for(j=1;j<=19;j++)
		for(i=1;i<=n;i++)
			fa[i][j]=fa[ fa[i][j-1] ][j-1];
	for(i=1;i<=q;i++)
	{
		scanf("%s%d%d",p,&x,&y);
		x^=ans;y^=ans;
		if(p[0]=='Q')
		{
			scanf("%d",&k);k^=ans;
			int lca=LCA(x,y);
			printf("%d\n", ans=Get_Kth(tree[x],tree[y],tree[lca],tree[fa[lca][0]],0,1000000000,k) );
		}
		else
		{
			if(siz[Find(x)]>siz[Find(y)])
				swap(x,y);
			top=0;
			DFS2(x,y);
			Unite(x,y);
			Add(x,y),Add(y,x);
			for(j=1;j<=19;j++)
				for(k=1;k<=top;k++)
					fa[ stack[k] ][j]=fa[ fa[ stack[k] ][j-1] ][j-1];
		}
	}

}
时间: 2024-10-13 15:48:55

BZOJ 3123 SDOI2013 森林 可持久化线段树+倍增LCA+启发式合并的相关文章

BZOJ 3123 SDOI 2013 森林 可持久化线段树+启发式合并

题目大意:给出一个森林,每个节点都有一个权值.有若干加边操作,问两点之间路径上的第k小权值是多少. 思路:这题和COT1比较像,但是多了连接操作.这样就只能暴力合并连个树.启发式合并会保证时间复杂度不至于太大.然后就是用可持久化线段树维护一个树的信息,按照dfs序来建树,每个节点的可持久化链的参考版本就是它父亲的版本.之后利用权值线段树可区间加减的特性,用f[x] + f[y] - f[lca] - f[father[lca]]来计算权值. CODE: #include <cstdio> #i

BZOJ 3524 [Poi2014]Couriers 可持久化线段树

题意:链接 方法:可持久化线段树. 解析: 可持久化数据结构好神啊,感觉都好玄妙的感觉. 首先建树的目的就是建立一棵权值树,维护的是在L,R里某些权值的数的出现个数.然后呢,对于1~n每个节点建一棵树,并且是基于前一棵树的基础上的.然后对于每一次的新值我们只需要update一次,并且连接一下原来的树? 神犇们不是说这种结构就是一堆线段树连啊连就出来了吗. 查询的时候呢?有一些小改变,据说是以二分为基础的查询. 神犇们发明这种数据结构的时候,就发现了这种数据结构里的所有线段树是可以相减的这种性质,

BZOJ 2588 Count on a tree 主席树+倍增LCA

题目大意:给定一棵树,每个节点有权值,询问两个节点路径上的权值第k小 这题很卡时间... 树链剖分+二分+树套树的O(nlog^4n)做法可以去死了 没有修改操作,树链剖分+二分+划分树O(nlog^3n),还是死了 我怒了,裸学了一发可持久化线段树(不看任何代码OTZ,我是怎么做到的0.0),二分+主席树,O(nlog^2n),居然还是死了! 最后发现我SB了,完全没有必要二分,直接把4个参全传下去就行了,O(nlogn) 首先我们对于每个节点维护这个节点到根的权值线段树 然后对于每个询问(x

bzoj 2653 middle (可持久化线段树)

middle Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1981  Solved: 1097[Submit][Status][Discuss] Description 一个长度为n的序列a,设其排过序之后为b,其中位数定义为b[n/2],其中a,b从0开始标号,除法取下整.给你一个 长度为n的序列s.回答Q个这样的询问:s的左端点在[a,b]之间,右端点在[c,d]之间的子序列中,最大的中位数. 其中a<b<c<d.位置也从0开始标号

bzoj 3123: [Sdoi2013]森林

如果题号没记错的话,2588是一个树上的主席树查询.这个题就是多了个合并而已.每一次都把小的合并到大的上就好了(所谓启发式2333) (主席树真是个好东西2333) (上部分为本蒟蒻不知道为什么RE到死都RE的代码,,,挖坑) (个人感觉主席树这种东西里离散不离散没什么区别的(常数而已),毕竟是log的,大个几次方就是多了个常数而已) 1 /*#include<bits/stdc++.h> 2 #define N 100005 3 #define M 40000005 4 #define LL

[BZOJ 3207] 花神的嘲讽计划Ⅰ【Hash + 可持久化线段树】

题目链接:BZOJ - 3207 题目分析 先使用Hash,把每个长度为 k 的序列转为一个整数,然后题目就转化为了询问某个区间内有没有整数 x . 这一步可以使用可持久化线段树来做,虽然感觉可以有更简单的做法,但是我没有什么想法... 代码 #include <iostream> #include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> #inclu

[BZOJ 3218] A + B Problem 【可持久化线段树 + 网络流】

题目连接:BZOJ - 3218 题目分析 题目要求将 n 个点染成黑色或白色,那么我们可以转化为一个最小割模型. 我们规定一个点 i 最后属于 S 集表示染成黑色,属于 T 集表示染成白色,那么对于每个点 i 就要连边 (S, i, B[i]) 和 (i, T, W[i]). 这样,如果一个点属于 S 集,就要割掉与 T 相连的边,就相当于失去了染成白色的收益. 我们再来考虑 “奇怪的点”,一个点 i 变成奇怪的点的条件是:i 是黑色且存在一个白色点 j 满足 j < i && L

【BZOJ 3674】可持久化并查集加强版&amp;【BZOJ 3673】可持久化并查集 by zky 用可持久化线段树破之

最后还是去掉异或顺手A了3673,,, 并查集其实就是fa数组,我们只需要维护这个fa数组,用可持久化线段树就行啦 1:判断是否属于同一集合,我加了路径压缩. 2:直接把跟的值指向root[k]的值破之. 3:输出判断即可. 难者不会,会者不难,1h前我还在膜这道题,现在吗hhh就当支持下zky学长出的题了. 3673: #include<cstdio> #include<cstring> #include<algorithm> #define read(x) x=ge

BZOJ 3439 Kpm的MC密码 Trie+可持久化线段树

题目大意:定义一种串,如果一个串是另一个串的后缀,那么这个串称作kpm串.问一个串的标号第k大的kpm串是多少. 思路:将所有的串翻转之后变成前缀,全都插进一个Trie树中.每个节点维护一个last指针,表示最后一次更新的可持久化线段树的指针,如果再有串经过这里,就继续更新last指针.最后只需要查询last指针中的东西就可以了. CODE: #include <cstdio> #include <cstring> #include <iostream> #includ