7076. 【2021.05.08 NOI模拟】膜拜

给出两个点数相同的树。若干次询问,每次给出一个点集(\([1,n]\)中的数,一个数表示着两棵树上对应的点),要求得到另一个包含这个点集的点集,满足该点集在两棵树上的点分别联通。

给出权值\(w_i\),求答案点集的\(\sum w_i\)。

\(n\le 10^5\)


一条链+一棵树的部分分做法:类似于之前的连续段问题处理即可(扫右端点,在左端点处维护连通块数,通过点-边得到连通块数,可以线段树上二分找到连续段。将询问丢入以左端点为关键字的大根堆中。对于当前右端点,找到最大左端点使其是连续段,处理左端点包含在其中的询问),时间\(O(n\lg n)\)。

正解:如果集合\(S,T\)合法,则\(S\bigcup T,S\bigcap T\)合法。在包含询问点集的极小连通块中,对于每条边\((u,v)\),找到包含\(u,v\)的极小合法点集,将它们并起来就是答案。

\(T_1\)中的边\((u,v)\)如果被选,需要\(T_2\)中\(u,v\)路径上的所有边被选。反之类似。根据这种选什么就必须选什么这样的关系连有向边(此时原来的边视作一个点),从\((u,v)\)对应点出发,能到达的所有点都要贡献。

用ST表优化连边,跑Tarjan即可。时间\(O(n\lg n)\)。

也可以树剖优化连边,时间\(O(n\lg^2 n)\)但跑得极快。(不过应该也可知用全局平衡二叉树得到\(O(n\lg n)\))


using namespace std;
#include <bits/stdc++.h>
#define N 100005
#define M (N*40)
#define fi first
#define se second
#define mp make_pair
int n,Q;
int w[N];
struct EDGE{
	int to;
	EDGE *las;
};
template<int V,int E>
struct Graph{
	EDGE e[E];
	int ne;
	EDGE *last[V];
	void link(int u,int v){
		e[ne]=(EDGE){v,last[u]};
		last[u]=e+ne++;
	}
};
Graph<M,M*2> F;
int m,c[M];
struct Tree{
	Graph<N,N*2> G;
	int fa[N][18],dep[N],id[N][18];
	void predfs(int x){
		for (int i=1;1<<i<=dep[x];++i){
			fa[x][i]=fa[fa[x][i-1]][i-1];
			c[++m]=max(c[id[x][i-1]],c[id[fa[x][i-1]][i-1]]);
			id[x][i]=m;
			F.link(id[x][i],id[x][i-1]);
			F.link(id[x][i],id[fa[x][i-1]][i-1]);
		}
		for (EDGE *ei=G.last[x];ei;ei=ei->las)
			if (ei->to!=fa[x][0]){
				fa[ei->to][0]=x;
				c[++m]=max(w[x],w[ei->to]);
				id[ei->to][0]=m;
				dep[ei->to]=dep[x]+1;
				predfs(ei->to);
			}
	}
	void init(){
		for (int i=1;i<n;++i){
			int u,v;
			scanf("%d%d",&u,&v);
			G.link(u,v),G.link(v,u);
		}
		predfs(1);
	}
	void lk(int x,int u,int v){
		if (dep[u]<dep[v]) swap(u,v);
		for (int k=dep[u]-dep[v],i=0;k;k>>=1,++i)
			if (k&1){
				F.link(x,id[u][i]);
				u=fa[u][i];
			}
		if (u==v) return;
		for (int i=17;i>=0;--i)
			if (fa[u][i]!=fa[v][i]){
				F.link(x,id[u][i]);
				F.link(x,id[v][i]);
				u=fa[u][i],v=fa[v][i];
			}
		F.link(x,id[u][0]);
		F.link(x,id[v][0]);
	}
	int qry(int u,int v){
		int res=0;
		if (dep[u]<dep[v]) swap(u,v);
		for (int k=dep[u]-dep[v],i=0;k;k>>=1,++i)
			if (k&1){
				res=max(res,c[id[u][i]]);
				u=fa[u][i];
			}
		if (u==v) return res;
		for (int i=17;i>=0;--i)
			if (fa[u][i]!=fa[v][i]){
				res=max(res,max(c[id[u][i]],c[id[v][i]]));
				u=fa[u][i],v=fa[v][i];
			}
		res=max(res,max(c[id[u][0]],c[id[v][0]]));
		return res;
	}
} T[2];
int dfn[M],low[M],nowdfn;
int st[M],tp;
bool ins[M];
void tarjan(int x){
	dfn[x]=low[x]=++nowdfn;
	st[++tp]=x;
	ins[x]=1;
	for (EDGE *ei=F.last[x];ei;ei=ei->las){
		if (!dfn[ei->to]){
			tarjan(ei->to),low[x]=min(low[x],low[ei->to]);
			c[x]=max(c[x],c[ei->to]);
		}
		else if (ins[ei->to])
			low[x]=min(low[x],dfn[ei->to]);
		else
			c[x]=max(c[x],c[ei->to]);
	}
	if (dfn[x]==low[x]){
		int v=c[x];
		for (int i=tp;st[i]!=x;--i)
			v=max(v,c[st[i]]);
		for (;st[tp]!=x;--tp)
			ins[st[tp]]=0,c[st[tp]]=v;
		ins[x]=0,c[x]=v,--tp;
	}
}
int main(){
	freopen("orz.in","r",stdin);
	freopen("orz.out","w",stdout);
	scanf("%d%d",&n,&Q);
	for (int i=1;i<=n;++i)
		scanf("%d",&w[i]);
	T[0].init();
	T[1].init();
	for (int i=2;i<=n;++i){
		T[1].lk(T[0].id[i][0],i,T[0].fa[i][0]);
		T[0].lk(T[1].id[i][0],i,T[1].fa[i][0]);
	}
	for (int i=1;i<=m;++i)
		if (!dfn[i])
			tarjan(i);
	while (Q--){
		int k,rt,ans;
		scanf("%d%d",&k,&rt);
		ans=w[rt];
		for (int i=1,x;i<k;++i){
			scanf("%d",&x);
			ans=max(ans,T[0].qry(rt,x));
		}
		printf("%d\n",ans);
	}
	return 0;
}
上一篇:再见,2016;你好,2017。


下一篇:[2013.9.15]关于string::nops为什么是-1