2021 ICPC SH H. Life is a Game
题目链接
原本想的是对询问离线,从小到大考虑,然后维护每个点能到达的集合。但是这个集合间的连边,因为与本身的点权和有关,所以是单向的,即是加了虚点之后也不太会维护优先考虑的边。(感觉不要刻意往离线想,会变得不幸)
如果对克鲁斯卡尔重构树有正确的理解,就会发现:虽然在连边的时候,限制与集合的点权和以及边权有关,但是对于每一个集合,都还是要优先考虑它向外连边中边权最小的。只要基于这一点,我们就可以按照边权从小到大考虑构建克鲁斯卡尔重构树。查询的时候,每个点能到达的集合,一定是在克鲁斯卡尔重构树向上爬能到达的最高点的子树中的原来的点,(充分性即从最高点往下走的边权不会更大;必要性即构建重构树的方式,保证了其不能再往上走的时候,也不能走到其它点),询问时倍增即可。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m,q,a[N],fa[N][18],wl[N][18];
struct node{
	int u,v,w;
}e[N];
bool cmp(node x,node y){
	return x.w<y.w;
}
struct DSU{
	int fa[N];
	void init(int n){
		for(int i=1;i<=n;i++) fa[i]=i;
	}
	int find(int u){
		if(u==fa[u]) return u;
		return fa[u]=find(fa[u]);
	}
}dsu;
int main()
{
	//srand(time(0));
	//freopen("1.in","r",stdin);
	//freopen("1.out","w",stdout);
	//int T; cin>>T; while(T--) work();
	cin>>n>>m>>q;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=m;i++) scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
	sort(e+1,e+m+1,cmp);
	dsu.init(n+n-1);
	int t=n;
	//memset(wl,0x3f,sizeof(wl));
	for(int i=1;i<=m;i++){
		int u=e[i].u,v=e[i].v,w=e[i].w;
		u=dsu.find(u); v=dsu.find(v);
		if(u==v) continue;
		t++;
		fa[u][0]=t; wl[u][0]=w;
		fa[v][0]=t; wl[v][0]=w;
		dsu.fa[u]=t; dsu.fa[v]=t;
	}
	for(int i=1;i<n+n;i++) a[fa[i][0]]+=a[i];
	for(int i=n+n-1;i;i--){
		wl[i][0]-=a[i];
		//cout<<i<<" a="<<a[i]<<" fa="<<fa[i][0]<<" wl="<<wl[i][0]<<endl;
		for(int j=1;j<18;j++) fa[i][j]=fa[fa[i][j-1]][j-1],wl[i][j]=max(wl[i][j-1],wl[fa[i][j-1]][j-1]);
	}
	while(q--){
		int x,k;
		scanf("%d%d",&x,&k);
		for(int i=17;~i;i--){
			if(fa[x][i] && wl[x][i]<=k) x=fa[x][i];
		}
		//cout<<"x="<<x<<endl;
		printf("%d\n",a[x]+k);
	}
	return 0;
}
 
                    
                 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号