HDU 5398 GCD Tree

题目链接

题意:

Teacher Mai has a graph with n vertices numbered from 1 to n. For every edge(u,v), the weight is gcd(u,v). (gcd(u,v) means the greatest common divisor of number u and v).

You need to find a subset of the edges that forms a tree that includes every vertex, where the total weight of all the edges in the tree is maximized. Print the total weight of these edges.

有n个点,将这些点建树,如果u与v之间有边相连,其边权为gcd(u,v) 请你找到一种建树的方案使边权和最大,对于每次询问的n,输出最大的边权和

思路:

首先,每一次加入一个点,只有和它的约数相连才会有贡献。比如当加入6的时候,优先考虑的应该是和3或2相连而不会去和5相连。

刚开始以为每一次直接和最大的因子相连贡献最大,但是这样子很显然是有问题的。

当加入一个点 6时,第一个考虑和3相连此时6的贡献为3,如果将1和3的边断掉,将6与2相连后再将3接到6上,边权和会更大

由此可见问题就转换成了求最大生成树。

先考虑每个点只会与它的因子相连的时候存在贡献,那么对于点对(x,y) 来说(这里y是x的因子),存在两种情况:

1、如果x与y不在一棵树上,那么可以将其直接连接。

2、如果x与y​此时已经在一棵树上,那么直接加边显然会构成环。此时我们可以删掉路径中的一条边,再将这两个点相连。由于要使得答案变大,那么删掉的边的价值一定是要小于当前加入的价值,也就是说我们要找到路径最小的边权

路径上求最大最小很明显可以用LCT了这可是件好事情

然后由于是边权,可以将边转换为点,分别向两端连边。

#include<bits/stdc++.h>
#define M 100005
#define N 400005
#define ll long long
using namespace std;
int n,val[N],Mi[N],son[N][2],fa[N],rev[N],L[N],R[N];
vector<int>G[M];
ll Ans[M];
//LCT板子
#define ls son[x][0]
#define rs son[x][1]
bool Is(int x) {
	return (son[fa[x]][0]==x)||(son[fa[x]][1]==x);
}
bool chk(int x) {
	return son[fa[x]][1]==x;
}
void up(int x) {
	Mi[x]=x;
	if(val[Mi[ls]]<val[Mi[x]])Mi[x]=Mi[ls];
	if(val[Mi[rs]]<val[Mi[x]])Mi[x]=Mi[rs];
}
void reserve(int x) {
	if(!x)return;
	swap(ls,rs),rev[x]^=1;
}
void down(int x) {
	if(!rev[x])return;
	reserve(ls),reserve(rs),rev[x]=0;
}
void rotate(int x) {
	int y=fa[x],z=fa[y],k=chk(x),w=son[x][k^1];
	if(Is(y))son[z][chk(y)]=x;
	fa[x]=z,son[y][k]=w;
	if(w)fa[w]=y;
	son[x][k^1]=y,fa[y]=x;
	up(y),up(x);
}
int stk[M];
void splay(int x) {
	int tot=0,y=x;
	stk[++tot]=y;
	while(Is(y))stk[++tot]=fa[y],y=fa[y];
	while(tot)down(stk[tot--]);
	while(Is(x)) {
		int y=fa[x],z=fa[y];
		if(Is(y)) {
			if(chk(x)==chk(y))rotate(y);
			else rotate(x);
		}
		rotate(x);
	}
}
void access(int x) {
	for(int y=0; x; y=x,x=fa[x])
		splay(x),son[x][1]=y,up(x);
}
void makert(int x) {
	access(x),splay(x),reserve(x);
}
int rt(int x) {
	access(x),splay(x);
	down(x);
	while(ls)x=ls,down(x);
	return x;
}
int Query(int x,int y) {
	makert(x),access(y),splay(y);
	return Mi[y];
}
void link(int x,int y) {
	makert(x),fa[x]=y;
}
void cut(int x,int y) {
	makert(x),access(y),splay(y),fa[son[y][0]]=0,son[y][0]=0,up(y);
}
int main() {
	for(int i=1; i<M; i++)//预处理出来因子
		for(int j=i; j<M; j+=i)
			G[j].push_back(i);
	int now=0;
	val[0]=1e8;
	for(int i=1; i<M; i++) { //[1,n]的点
		val[++now]=1e8;
		Mi[now]=now;
	}
	ll tmp=0;
	for(int u=1; u<M; u++) {
		for(int j=G[u].size()-2; j>=0; j--) {
			int v=G[u][j];
			bool ok=1;
			if(rt(u)==rt(v)) {//如果已经相连考虑删边
				int k=Query(u,v);
				if(val[k]<v) {
					cut(L[k],k),cut(R[k],k);
					tmp-=val[k];
					ok=1;
				} else ok=0;
			}
			if(ok) {//说明可以连边
				val[++now]=v,Mi[now]=now,L[now]=u,R[now]=v;
				link(u,now),link(v,now);
				tmp+=v;
			}
		}
		Ans[u]=tmp;
	}
	while(~scanf("%d",&n))printf("%lld
",Ans[n]);
	return 0;
}
原文地址:https://www.cnblogs.com/cly1231/p/10784486.html