bzoj-3123 森林

题意:

给出一个n个点m条边的森林,每个点有一个点权,有两种操作;

1.查询两点之间的第K小的点权,保证合法;

2.连边(x,y);

m<n<=80000;

题解:

论正确姿势的重要性;

首先询问和某道COT的题很像,而这道题中多了Link操作;

然而,那道COT的题我是用树链剖分写的。。。

一开始的脑洞是每次将小的暴力重构作为一个轻链连在大的树上,然后每隔一段时间重构一次大树;

听起来十分暴力了。。鬼知道能不能过;

而正解是COT的正解的姿势,将一个询问分成四个,利用主席树的可减性质,找到LCA然后+x+y-LCA(x,y)-FA(LCA(x,y))像这样处理就可以了;

重构是差不多的,也是启发式合并,维护一下倍增LCA;

注意倍增LCA的数组要清。。小心RE,还是说那么写LCA的只有我一个= =?

空间给的足够的大,所以不需要写垃圾回收了,实际上因为每次重构的是整棵子树,回收是可以的但是有些麻烦。。;

总之还是一道挺好的题(笑);

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 81000
#define M 20002000
#define lson l,mid,ls[no]
#define rson mid+1,r,rs[no]
using namespace std;
typedef long long ll;
int next[N<<1],to[N<<1],head[N],ce;
int val[N],dis[N],len;
int deep[N],fa[N][22],root[N];
int size[M],ls[M],rs[M],tot;
char op[10];
namespace Set
{
	int size[N],f[N];
	void init(int n)
	{
		for(int i=1;i<=n;i++)
			size[i]=1,f[i]=i;
	}
	int find(int x)
	{
		return f[x]==x?x:f[x]=find(f[x]);
	}
	void Link(int x,int y)
	{
		x=find(x),y=find(y);
		if(x!=y)
		{
			if(size[x]>size[y])
				swap(x,y);
			size[y]+=size[x];
			f[x]=y;
		}
	}
}
int newnode()
{
	static int ret;
	ret=++tot;
	ls[ret]=rs[ret]=size[ret]=0;
	return ret;
}
void Insert(int l,int r,int &no,int val)
{
	int p=newnode();
	ls[p]=ls[no],rs[p]=rs[no],size[p]=size[no];
	no=p;
	size[no]++;
	if(l==r)	return ;
	int mid=l+r>>1;
	if(val<=mid)	Insert(lson,val);
	else			Insert(rson,val);
}
int query(int l,int r,int no1,int no2,int no3,int no4,int k)
{
	if(l==r)
		return l;
	else
	{
		int mid=l+r>>1;
		if(k<=size[ls[no1]]+size[ls[no2]]-size[ls[no3]]-size[ls[no4]])
			return query(l,mid,ls[no1],ls[no2],ls[no3],ls[no4],k);
		else
			return query(mid+1,r,rs[no1],rs[no2],rs[no3],rs[no4],
				k-size[ls[no1]]-size[ls[no2]]+size[ls[no3]]+size[ls[no4]]);
	}
}
void dfs(int x)
{
	deep[x]=deep[fa[x][0]]+1;
	root[x]=root[fa[x][0]];
	Insert(1,len,root[x],val[x]);
	memset(fa[x]+1,0,sizeof(int)*21);
	for(int i=1;fa[x][i-1];i++)
		fa[x][i]=fa[fa[x][i-1]][i-1];
	for(int i=head[x];i;i=next[i])
	{
		if(to[i]!=fa[x][0])
		{
			fa[to[i]][0]=x;
			dfs(to[i]);
		}
	}
}
int LCA(int x,int y)
{
	if(x==y)	return x;
	if(deep[x]<deep[y])
		swap(x,y);
	int k=20;
	while(k>=0)
	{
		if(deep[fa[x][k]]>=deep[y])
			x=fa[x][k];
		k--;
	}
	if(x==y)	return x;
	k=20;
	while(k>=0)
	{
		if(fa[x][k]!=fa[y][k])
			x=fa[x][k],y=fa[y][k];
		k--;
	}
	return fa[x][0];
}
void add(int x,int y)
{
	to[++ce]=y;
	next[ce]=head[x];
	head[x]=ce;
}
int main()
{
	int n,m,q,i,j,k,x,y,v,fx,fy,last;
	scanf("%*d");
	scanf("%d%d%d",&n,&m,&q);
	Set::init(n);
	for(i=1;i<=n;i++)
		scanf("%d",val+i),dis[i]=val[i];
	sort(dis+1,dis+n+1);
	len=unique(dis+1,dis+n+1)-dis-1;
	for(i=1;i<=n;i++)
		val[i]=lower_bound(dis+1,dis+len+1,val[i])-dis;
	for(i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y),add(y,x);
		Set::Link(x,y);
	}
	for(i=1;i<=n;i++)
	{
		if(!deep[i])
			dfs(i);
	}
	for(i=1,last=0;i<=q;i++)
	{
		scanf("%s%d%d",op,&x,&y);
		x^=last,y^=last;
		if(op[0]=='Q')
		{
			scanf("%d",&k);
			k^=last;
			v=LCA(x,y);
			last=dis[query(1,len,root[x],root[y],root[v],root[fa[v][0]],k)];
			printf("%d\n",last);
		}
		else
		{
			fx=Set::find(x),fy=Set::find(y);
			if(Set::size[fx]>Set::size[fy])
				swap(x,y),swap(fx,fy);
			Set::Link(x,y);
			fa[x][0]=y;
			dfs(x);
			add(x,y),add(y,x);
		}
	}
	return 0;
}
时间: 2024-08-26 04:42:57

bzoj-3123 森林的相关文章

BZOJ 3123 【SDOI2013】 森林

题目链接:森林 这道题想法很显然.既然只有加边而没有删边,那么每次启发式合并就可以了.查询路径\(k\)小似乎需要主席树,那么把主席树和倍增表一起暴力重构就好了. 然后发现这样的空间复杂度是\(O(n\log^2n)\)的.感觉非常不靠谱,于是滚去写了个节点回收站--然后发现主席树节点回收的话每个节点会被\(dfs\)到多次,还需要一些特判-- 然后就是一直\(RE\)--由于题目是强制在线的那么就应该是\(Wa\)了.但是对拍了\(6W\)组数据没出错我还能说什么呢-- 最后突然发现,我的倍增

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

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

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

题目大意:给定一棵森林,每个点有权值,提供两种操作: 1.查询两点间路径上第k小的权值 2.将两个点之间连一条边 保证连接后仍是一座森林 可持久化线段树部分同Count On A Tree 只是这道题加了个连接操作 对于连接操作我们要用到启发式合并 就是把小的那棵树暴力重建 很简单的一个操作但是可以证明是均摊O(nlogn)的 大小我用了并查集 其实记录根就可以了 此外本题的多组数据是在逗比 记住testcase恒等于1就是了 NND我倍增LCA又写错了0.0 预处理时居然从大往小写的0.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 3123】 [Sdoi2013]森林 主席树启发式合并

我们直接按父子关系建主席树,然后记录倍增方便以后求LCA,同时用并查集维护根节点,而且还要记录根节点对应的size,用来对其启发式合并,然后每当我们合并的时候我们都要暴力拆小的一部分重复以上部分,总时间复杂度为O(n*log),因为每个的节点只会作为小的部分合并,因此他所在的一小部分至少变大2倍,对于每一个节点他作为被合并方最多log次,因此其复杂度为O(n*log),而这个是绝对跑不满还差很多的,我们视他为无常数就好了,当然这一切都是建立在无拆分操作的基础之上,只要有了拆分启发式合并的复杂度就

[BZOJ 1146]网络管理Network 树上带修改路径k值

题目意思非常清楚,就是要求树上带修改的路径k大值 如果不带修改的话,我会用树上主席树去搞,以父子关系建树,可以参见 [BZOJ 3123]森林 但是带修改就不会打了QAQ,于是去学了另一种在dfs序上搞的方法(同时感谢呵呵酵母菌的帮助) 其实思想是一样的,就是搞出来节点到根路径的线段树,然后用x+y-lca-fa(lca)去差分出来树上路径的线段树,再去里面查询k值 那么我们怎么得到点到根路径的线段树呢?可以在dfs序上差分啊! dfs序列上的dfnl[x]~dfnr[x]包含的是以x为根节点的

[省选]省选知识点进度

联赛之后记录一下自己的知识点学习情况(按开始时间先后顺序) 可持久化数据结构 [BZOJ 3123]森林 树上主席树 启发式合并 LCA [BZOJ 4826]影魔 区间修改主席树 标记永久化 [BZOJ 2735]世博会 主席树 切比雪夫距离转曼哈顿距离 [BZOJ 3166]Alo 可持久化01Trie [BZOJ 3689]异或之 可持久化01Trie [BZOJ 3261]最大异或和 可持久化01Trie 树套树 [COGS 257]动态排名系统 树状数组套主席树 [BZOJ 2141]

YCB 的暑期计划

前言 YCB现在很弱(TAT) 暑假有一个月,赶快狂补一下. 大概的计划如下: 首先前期会以数据结构为主,毕竟代码能力太弱,涉及内容:线段树分治.二进制分组.KD-Tree. 等数据结构做到没有智商的时候加入一波数论,内容为 杜教筛.min_25筛. 然后中途小清新一下,做一些 组合博弈与构造题. 接着继续练代码能力,顺便学一些神奇的暴力:启发式合并.dsu on tree . 然后图论也忘的差不多了,就回过头去学点新东西,大概会有spfa判负环.0/1分数规划.差分约束. 估计这个时候也没有什

bzoj 3669: [Noi2014]魔法森林

3669: [Noi2014]魔法森林 Time Limit: 30 Sec  Memory Limit: 512 MB 动点spfa Description 为了得到书法大家的真传,小E同学下定决心去拜访住在魔法森林中的隐士.魔法森林可以被看成一个包含个N节点M条边的无向图,节点标号为1..N,边标号为1..M.初始时小E同学在号节点1,隐士则住在号节点N.小E需要通过这一片魔法森林,才能够拜访到隐士. 魔法森林中居住了一些妖怪.每当有人经过一条边的时候,这条边上的妖怪就会对其发起攻击.幸运的

【BZOJ】【3669】【NOI2014】魔法森林

LCT动态维护MST LCT动态维护MST 我们可以枚举a,然后找从1到n的一条路径使得:这条路径上的b的最大值最小.这个路径肯定在MST上……所以枚举一遍所有的边,动态维护一个关于b值的MST即可. 调了半天没出解的原因: rotate写错了……l=c[y][1]==x 我写成了 l=c[z][1]==y sigh…… 1 /************************************************************** 2 Problem: 3669 3 User