bzoj 3551: [ONTAK2010]Peaks加强版 最小生成树&主席树

       我们如果首先求出了最小生成树,那么在询问的时候去掉所有边权>x的边,那么剩下的和v相连的就是可以走到的山峰。

       那么考虑在并查集的时候做一点科(shou)技(jiao),比如现在要合并u和v所在的连通块,边权为t,那么显然u中所有点到v中所有点的路径中的最大边权为t,那么新建一个点p,连p->u和p->v且p的点权为t。那么n-1合并之后得到了一颗有n的叶子节点的树,那么两个点x->y的路径中的最大值即它们lca的点权。

       这样在操作的时候就可以找到v的最上面的一个<=x的祖先(显然点权随着深度递增),那么这个祖先的子树中的所有点就是v所能到达的点。因此只需要维护某一个点的子树的信息即可。

       求子树中第k小点,可以用dfs序+主席树;也可以每个点保存一颗线段树然后合并。

AC代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200005
#define M 500005
using namespace std;

int n,m,pt,cnt,dfsclk,tot,trtot,bin[25],a[N],num[N],fa[N],d[N],anc[N][17],fst[N],pnt[M],nxt[M];
int pos[N][2],id[N],rt[N],ls[4000005],rs[4000005],sum[4000005];
struct edg{ int x,y,z; }e[M];
int read(){
	int x=0; char ch=getchar();
	while (ch<'0' || ch>'9') ch=getchar();
	while (ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar(); }
	return x;
}
int getfa(int x){ return (x==fa[x])?x:fa[x]=getfa(fa[x]); }
void add(int x,int y){
	pnt[++tot]=y; nxt[tot]=fst[x]; fst[x]=tot;
}
int find(int x){
	int l=0,r=cnt,mid;
	while (l<r){
		mid=(l+r)>>1;
		if (num[mid]<x) l=mid+1; else r=mid;
	}
	return l;
}
void dfs(int x){
	pos[x][0]=pos[x][1]=++dfsclk; id[dfsclk]=x; int p,i;
	for (i=1; bin[i]<=d[x]; i++) anc[x][i]=anc[anc[x][i-1]][i-1];
	for (p=fst[x]; p; p=nxt[p]){
		int y=pnt[p];
		anc[y][0]=x; d[y]=d[x]+1;
		dfs(y); pos[x][1]=pos[y][1];
	}
}
int getanc(int x,int t){
	int i; if (!t) return x;
	for (i=16; i>=0; i--){
		if (bin[i]<=d[x] && a[anc[x][i]]<=t) x=anc[x][i];
	}
	return x;
}
void ins(int l,int r,int x,int &y,int k){
	y=++trtot; sum[y]=sum[x]+1;
	if (l==r) return; int mid=(l+r)>>1;
	if (k<=mid){ rs[y]=rs[x]; ins(l,mid,ls[x],ls[y],k); }
	else{ ls[y]=ls[x]; ins(mid+1,r,rs[x],rs[y],k); }
}
int qry(int x,int y,int z){
	int l=1,r=cnt,mid,tmp; x=rt[x-1]; y=rt[y];
	if (sum[y]-sum[x]<z) return -1; z=sum[y]-sum[x]-z+1;
	while (l<r){
		mid=(l+r)>>1; tmp=sum[ls[y]]-sum[ls[x]];
		if (tmp>=z){ x=ls[x]; y=ls[y]; r=mid; }
		else{ x=rs[x]; y=rs[y]; z-=tmp; l=mid+1; }
	}
	return num[l];
}
bool cmp(edg u,edg v){ return u.z<v.z; }
int main(){
	n=read(); m=read(); int i,cas=read();
	bin[0]=1; for (i=1; i<=17; i++) bin[i]=bin[i-1]<<1;
	for (i=1; i<=n; i++) a[i]=num[i]=read();
	sort(num+1,num+n+1);
	cnt=1;
	for (i=2; i<=n; i++)
		if (num[i]!=num[cnt]) num[++cnt]=num[i];
	for (i=1; i<=n; i++) a[i]=find(a[i]);
	for (i=1; i<=m; i++){
		e[i].x=read(); e[i].y=read(); e[i].z=read();
	}
	sort(e+1,e+m+1,cmp); pt=n; int k,x,y;
	for (i=1; i<(n<<1); i++) fa[i]=i;
	for (i=1; i<=m; i++){
		x=getfa(e[i].x); y=getfa(e[i].y);
		if (x!=y){
			fa[x]=fa[y]=++pt; a[pt]=e[i].z;
			add(pt,x); add(pt,y);
			if (pt+1==(n<<1)) break;
		}
	}
	for (i=1; i<=n; i++){
		x=getfa(i);
		if (!pos[x][0]) dfs(x);
	}
	for (i=1; i<=pt; i++)
		if (id[i]<=n) ins(1,cnt,rt[i-1],rt[i],a[id[i]]);
		else rt[i]=rt[i-1];
	int ans=0;
	while (cas--){
		x=read()^ans; y=read()^ans; k=read()^ans;
		x=getanc(x,y);
		printf("%d\n",ans=qry(pos[x][0],pos[x][1],k));
		if (ans<0) ans=0;
	}
	return 0;
}


by lych

2016.3.30

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值