bzoj4539: [Hnoi2016]树

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4539

思路:首先把大树缩点,一个点代表一次操作复制的子树

两个点之间的边权值就是两个子树的根在大树中的距离,这个可以在原树中用倍增求出

至于从大树标号转成原树标号,就相当于求子树内编号第k大的点的编号,用可持久化线段树即可。

询问的话,就先把两个点移到对应复制操作的子树的根,计算距离,再在缩好点的大树里跳到lca,计算距离,再把lca多算的那段减掉即可。

考场上想到了60分,突然就不会求区间第k大了,然后就放弃了这题...

细节较多,一波大讨论即可。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
typedef long long ll;
const ll maxn=100010,maxm=200010,maxk=22,maxt=2200000;
using namespace std;
int n,m,Q,dfn[maxn],last[maxn],tim,pw[maxk];
int root[maxn],from[maxn],idx;ll cnt[maxn],nn;
//root 该点表示子树的根,from该点接在原树哪个点下面,cnt该次操作后新树的大小,nn新树大小
void read(int &x){
	char ch;
	for (ch=getchar();!isdigit(ch);ch=getchar());
	for (x=0;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
}
void read(ll &x){
	char ch;
	for (ch=getchar();!isdigit(ch);ch=getchar());
	for (x=0;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
}
struct Tsegment{
	#define ls ch[p][0]
	#define rs ch[p][1]
	#define mid ((l+r)>>1)
	int siz[maxt],ch[maxt][2],root[maxn],tot;
	void insert(int pre,int &p,int l,int r,int x){
		p=++tot,siz[p]=siz[pre]+1;
		if (l==r) return;
		if (x<=mid) rs=ch[pre][1],insert(ch[pre][0],ls,l,mid,x);
		else ls=ch[pre][0],insert(ch[pre][1],rs,mid+1,r,x);
	}
	void insert(int id,int v){insert(root[id-1],root[id],1,n,v);}
	int query(int pre,int p,int l,int r,int k){
		if (l==r){return l;}
		if (siz[ls]-siz[ch[pre][0]]>=k) return query(ch[pre][0],ls,l,mid,k);
		else return query(ch[pre][1],rs,mid+1,r,k-(siz[ls]-siz[ch[pre][0]]));
	}
	int query(int x,int y,int k){return query(root[x-1],root[y],1,n,k);}
}T;

struct Tgraph1{
	int pre[maxm],now[maxn],son[maxm],tot,dep[maxn],fa[maxn][maxk],siz[maxn];
	ll dis[maxn],val[maxm];
	void add(int a,int b,ll c){pre[++tot]=now[a],now[a]=tot,son[tot]=b,val[tot]=c;}
	void ins(int a,int b,ll c){add(a,b,c),add(b,a,c);}
	void dfs(int x){
		for (int i=1;i<=18;i++) fa[x][i]=fa[fa[x][i-1]][i-1];
		for (int y=now[x];y;y=pre[y]) if (son[y]!=fa[x][0])
			fa[son[y]][0]=x,dis[son[y]]=dis[x]+val[y],dep[son[y]]=dep[x]+1,dfs(son[y]);
	}
	void dfs2(int x){
		siz[x]=1,dfn[x]=++tim,T.insert(tim,x);
		for (ll y=now[x];y;y=pre[y]) if (son[y]!=fa[x][0]) dfs2(son[y]),siz[x]+=siz[son[y]];
		last[x]=tim;
	}
	void jump(int &x,int h){for (int i=18;i>=0;i--) if (h&pw[i]) x=fa[x][i],h-=pw[i];}
	int lca(int x,int y){
		if (dep[x]<dep[y]) swap(x,y);
		jump(x,dep[x]-dep[y]);
		if (x==y) return x;
		for (int i=18;i>=0;i--) if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
		return fa[x][0];
	}
	ll dist(int x,int y){return dis[x]+dis[y]-2*dis[lca(x,y)];}
	int up(int x,int y){jump(x,dep[x]-dep[y]-1);return x;}
}ori,g;
int getid(ll x){return lower_bound(cnt+1,cnt+1+idx,x)-cnt;}

ll query(ll a,ll b){
	int ida=getid(a),rta=root[ida],aa=T.query(dfn[rta],last[rta],a-cnt[ida-1]);
	int idb=getid(b),rtb=root[idb],bb=T.query(dfn[rtb],last[rtb],b-cnt[idb-1]);
	int lca=g.lca(ida,idb);
	if (ida==idb) return ori.dist(aa,bb);
	ll res=g.dist(ida,idb)+ori.dis[aa]-ori.dis[rta]+ori.dis[bb]-ori.dis[rtb];
	if (ida==lca){
		int frb=from[g.up(idb,lca)];
		res-=ori.dis[aa]+ori.dis[frb]-ori.dist(aa,frb)-2*ori.dis[rta];
	}
	else if (idb==lca){
		int fra=from[g.up(ida,lca)];
		res-=ori.dis[bb]+ori.dis[fra]-ori.dist(bb,fra)-2*ori.dis[rtb];
	}
	else{
		int fra=from[g.up(ida,lca)];
		int frb=from[g.up(idb,lca)];
		res-=ori.dis[fra]+ori.dis[frb]-ori.dist(fra,frb)-2*ori.dis[root[lca]];
	}
	return res;
}

int main(){
	//freopen("tree.in","r",stdin);freopen("tree.out","w",stdout);
	scanf("%d%d%d",&n,&m,&Q);
	pw[0]=1;for (int i=1;i<=19;i++) pw[i]=pw[i-1]<<1;
	for (int i=1,x,y;i<n;i++) read(x),read(y),ori.ins(x,y,1);
	ori.dfs(1),ori.dfs2(1);
	nn=n,cnt[1]=nn,root[1]=idx=1;
	for (int i=2;i<=m+1;i++){
		ll x,y;
		read(x),read(y);
		int id=getid(y),rt=root[id];
		root[i]=x,idx=i,from[i]=T.query(dfn[rt],last[rt],y-cnt[id-1]);
		g.ins(i,id,ori.dis[from[i]]-ori.dis[rt]+1);
		nn+=ori.siz[x],cnt[i]=nn;
	}
	g.dfs(1);
	for (int i=1;i<=Q;i++){
		ll a,b;read(a),read(b);
		printf("%lld\n",query(a,b));
	}
	return 0;
}


你可能感兴趣的:(bzoj4539: [Hnoi2016]树)