题目链接
题意:
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;
}