BZOJ 3123 【SDOI2013】 森林

题目链接:森林

  这道题想法很显然。既然只有加边而没有删边,那么每次启发式合并就可以了。查询路径\(k\)小似乎需要主席树,那么把主席树和倍增表一起暴力重构就好了。

  然后发现这样的空间复杂度是\(O(n\log^2n)\)的。感觉非常不靠谱,于是滚去写了个节点回收站……然后发现主席树节点回收的话每个节点会被\(dfs\)到多次,还需要一些特判……

  然后就是一直\(RE\)……由于题目是强制在线的那么就应该是\(Wa\)了。但是对拍了\(6W\)组数据没出错我还能说什么呢……

  最后突然发现,我的倍增表在合并的时候好像没有清空……本来以为这样不会出问题,但是由于我的倍增的一些写法,就跳到了一些奇怪的节点上去……以后还是不那么写了,老老实实从上界开始\(for\)吧

  下面贴代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
#define maxn 80010
#define MAXN 1500010

using namespace std;
typedef long long llg;

int n,m,T,d[maxn],ld,rt[maxn],a[maxn],qz,lans;
int head[maxn],next[maxn<<1],to[maxn<<1],tt;
int sumv[MAXN],le[MAXN],ri[MAXN],q[MAXN],lq,rq,L;
int f[maxn][17],dep[maxn],fa[maxn],siz[maxn];
bool isin[MAXN];

int getint(){
	int w=0;bool q=0;
	char c=getchar();
	while((c>‘9‘||c<‘0‘)&&c!=‘-‘) c=getchar();
	if(c==‘-‘) c=getchar(),q=1;
	while(c>=‘0‘&&c<=‘9‘) w=w*10+c-‘0‘,c=getchar();
	return q?-w:w;
}

int find(int x){return fa[fa[x]]==fa[x]?fa[x]:fa[x]=find(fa[x]);}
void qpush(int x){if(isin[x]) return; isin[x]=1;q[rq++]=x,rq%=MAXN;}
int qtop(){
	if(lq==rq) return ++qz;
	int now=q[lq++]; lq%=MAXN;
	isin[now]=0; return now;
}

void link(int x,int y){
	to[++tt]=y;next[tt]=head[x];head[x]=tt;
	to[++tt]=x;next[tt]=head[y];head[y]=tt;
}

int build(int u,int l,int r){
	int now=qtop(),mid=(l+r)>>1;
	le[now]=le[u]; ri[now]=ri[u];
	sumv[now]=sumv[u]+1;
	if(l!=r){
		if(L<=mid) le[now]=build(le[u],l,mid);
		else ri[now]=build(ri[u],mid+1,r);
	}
	return now;
}

void dfs(int u,int fr){
	dep[u]=dep[fr]+1; f[u][0]=fr;
	if(!fa[u]) fa[u]=(fr?find(fr):u); siz[find(u)]++;
	L=a[u],rt[u]=build(rt[fr],1,ld);
	for(int i=1;i<17;i++)
		f[u][i]=f[f[u][i-1]][i-1];
	for(int i=head[u];i;i=next[i])
		if(to[i]!=fr) dfs(to[i],u);
}

void del(int u){if(!u || isin[u])return;qpush(u);del(le[u]);del(ri[u]);le[u]=ri[u]=0;}
void shan(int u,int fr){
	del(rt[u]);
	for(int i=head[u];i;i=next[i])
		if(to[i]!=fr) shan(to[i],u);
}

int lca(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=16;i>=0;i--)
		if(dep[f[u][i]]>=dep[v])
			u=f[u][i];
	if(u==v) return u;
	for(int i=16;i>=0;i--)
		if(f[u][i]!=f[v][i])
			u=f[u][i],v=f[v][i];
	return f[u][0];
}

void merge(int x,int y){
	int a=find(x),b=find(y);
	if(siz[a]>siz[b]) swap(x,y),swap(a,b);
	fa[a]=b; siz[b]+=siz[a];
	shan(a,0); link(x,y); dfs(x,y);
}

int query(int x,int y,int k){
	int p=lca(x,y),l=1,r=ld,mid,now,g;
	x=rt[x]; y=rt[y]; g=rt[p];
	while(l!=r){
		mid=(l+r)>>1; now=(a[p]>=l && a[p]<=mid);
		now+=sumv[le[x]]+sumv[le[y]]-(sumv[le[g]]<<1);
		if(k<=now) r=mid,x=le[x],y=le[y],g=le[g];
		else k-=now,l=mid+1,x=ri[x],y=ri[y],g=ri[g];
	}
	return l;
}

int main(){
	File("a");
	getint();
	n=getint(); m=getint(); T=getint();
	for(int i=1;i<=n;i++) d[++ld]=a[i]=getint();
	sort(d+1,d+ld+1); ld=unique(d+1,d+ld+1)-d-1;
	for(int i=1;i<=n;i++) a[i]=lower_bound(d+1,d+ld+1,a[i])-d;
	while(m--) link(getint(),getint());
	for(int i=1;i<=n;i++) if(!dep[i]) dfs(i,0);
	while(T--){
		char c=getchar(); int x,y;
		while(c!=‘Q‘ && c!=‘L‘) c=getchar();
		x=getint()^lans,y=getint()^lans;
		if(c==‘L‘) merge(x,y);
		else printf("%d\n",lans=d[query(x,y,getint()^lans)]);
	}
	return 0;
}
时间: 2024-08-15 03:24:57

BZOJ 3123 【SDOI2013】 森林的相关文章

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 3198: [Sdoi2013]spring 题解

[原题] 3198: [Sdoi2013]spring Time Limit: 40 Sec  Memory Limit: 256 MB Submit: 253  Solved: 95 Description Input Output Sample Input 3 3 1 2 3 4 5 6 1 2 3 0 0 0 0 0 0 4 5 6 Sample Output 2 HINT [题解]这道题明明是水题,坑了我两天!!!真是伤心.发现哈希都不熟练了. 首先很容易想到是2^6枚举01状态,使得1

Luogu_P3302 [SDOI2013]森林【题解】主席树 lca 启发式合并

# Luogu_P3302 [SDOI2013]森林 主席树,启发式合并,lca luogu题面 求树上路径的第k大,树之间还有合并. 明显是主席树再加合并. 先说链上第k大,其实就是$Tx+Ty-Tlca-Tlcafa$ $T$表示权值线段树. 主席树维护的是从根节点到当前节点的前缀和. ask的代码如下: inline int ask(int x,int y,int lcc,int lcf,int l,int r,int k){ if(l==r) return b[l]; int lz=su

[SDOI2013]森林(树上主席树)

[SDOI2013]森林(luogu) Description 题目描述 小Z有一片森林,含有N个节点,每个节点上都有一个非负整数作为权值.初始的时候,森林中有M条边. 小Z希望执行T个操作,操作有两类: Q x y k查询点x到点y路径上所有的权值中,第k小的权值是多少.此操作保证点x和点y连通,同时这两个节点的路径上至少有k个点. L x y在点x和点y之间连接一条边.保证完成此操作后,仍然是一片森林. 为了体现程序的在线性,我们把输入数据进行了加密.设lastans为程序上一次输出的结果,

【BZOJ 3123】 [Sdoi2013]森林 主席树启发式合并

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

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

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

[Sdoi2013]森林

/* 平常这种题很常见的思路就是求出dfs序来,然后每次查询的时候就是在主席树上查询 x+y-lca-fa[lca] 的值就行了. 但是这个题要动态的给森林中加边,还是强制在线的,所以就需要考虑换一种方法来维护这个东西. 首先先dfs出每棵树来,然后对于link操作,可以启发式合并两个主席树.这里我们把主席树维护的dfs序变成维护每个点到根的这条路径.所里link的时候假设我们要把x合到y上,那么我们就边dfs x 这棵树,边用当前点的fa作为历史状态的root来更新当前点的root就行了.求l

[BZOJ 3129] [Sdoi2013] 方程 【容斥+组合数取模+中国剩余定理】

题目链接:BZOJ - 3129 题目分析 使用隔板法的思想,如果没有任何限制条件,那么方案数就是 C(m - 1, n - 1). 如果有一个限制条件是 xi >= Ai ,那么我们就可以将 m 减去 Ai - 1 ,相当于将这一部分固定分给 xi,就转化为无限制的情况了. 如果有一些限制条件是 xi <= Ai 呢?直接来求就不行了,但是注意到这样的限制不超过 8 个,我们可以使用容斥原理来求. 考虑容斥:考虑哪些限制条件被违反了,也就是说,有哪些限制为 xi <= Ai 却是 xi