S
o
u
c
e
:
Souce:
Souce: 2015 Multi-University Training Contest 9
P
r
o
b
l
e
m
:
Problem:
Problem:
1e5个查询,每次给一个n,表示一张n个节点的完全图,点的标号从1到n,任意两点之间的边权为标号的gcd,求最大生成树。
I
d
e
a
:
Idea:
Idea:
如果只有单次查询,直接按gcd大小从后往前做就好了。
那么现在只能从前往后动态的加入第i个点,注意到每个点只跟约数连边是不会影响答案的,如果gcd(u,v)=d,那么断开这条边后,与d不连通的那个点就可以和d连边了。于是就可以用lct维护最大生成树啦。
C
o
d
e
:
Code:
Code:
#include<bits/stdc++.h>
using namespace std;
#define I inline
#define fi first
#define se second
#define pb push_back
#define ALL(X) (X).begin(), (X).end()
#define CLR(A, X) memset(A, X, sizeof(A))
typedef double DB;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
const int N = 1e5+10, M = 2e6+10;
LL now;
struct Edge { int u, v, w; } e[M];
I namespace lct {
#define pa T[x].fa
#define lc T[x].ch[0]
#define rc T[x].ch[1]
struct meow { int ch[2], fa, rev, mx, w, id; }T[N+M];
void init() {
for(int i = 0; i < N; i++) {
T[i].ch[0] = T[i].ch[1] = T[i].fa = T[i].rev = 0;
T[i].mx = T[i].w = -1e9; T[i].id = 0;
}
}//清空
I int wh(int x) { return T[pa].ch[1]==x; } //是否是右儿子
I int isrt(int x) { return T[pa].ch[0]!=x && T[pa].ch[1]!=x; } //是否是splay的根
I void pushup(int x) {
T[x].mx = T[x].w, T[x].id = x;
if(T[lc].mx > T[x].mx) T[x].mx = T[lc].mx, T[x].id = T[lc].id;
if(T[rc].mx > T[x].mx) T[x].mx = T[rc].mx, T[x].id = T[rc].id;
}
I void Reverse(int x) { T[x].rev ^= 1; swap(lc, rc); } //翻转
I void pushdown(int x) {
if(T[x].rev) {
if(lc) Reverse(lc);
if(rc) Reverse(rc);
T[x].rev = 0;
}
}
void pd(int x) { if(!isrt(x)) pd(pa); pushdown(x);}
I void Rotate(int x) {
int y=pa, z=T[y].fa, k=wh(x);
if(!isrt(y)) T[z].ch[wh(y)]=x; pa=z;
T[y].ch[k] = T[x].ch[k^1]; T[T[y].ch[k]].fa=y;
T[x].ch[k^1] = y; T[y].fa=x;
pushup(y); pushup(x);
}
I void splay(int x) {
pd(x);
for(; !isrt(x); Rotate(x)) if(!isrt(pa))
Rotate(wh(pa)==wh(x) ? pa : x);
}
I void access(int x) { for(int y=0; x; y=x, x=pa) splay(x), rc=y, pushup(x); }//访问
I void makert(int x) { access(x); splay(x); Reverse(x); } //换根
I int findrt(int x) { access(x); splay(x); while(lc) pushdown(x), x=lc; return x; }//找原根
I void link(int x, int y) { makert(x); pa=y; } //连边
I void split(int x, int y) { makert(x); access(y); splay(y); } //提取路径
I bool cut(int x, int y) {
split(x, y);
if(T[x].ch[1] || pa!=y) return 0; //不存在此边
pa = T[y].ch[0] = 0; pushup(y);
return 1;
}//断边
I void merge(int x, int y, int id) {
bool flag = 0;
if(findrt(x) != findrt(y)) flag = 1, now += e[id].w;
else {
split(x, y); int z = T[y].id;
if(T[z].w > e[id].w) {
flag = 1;
now += e[id].w;
now -= T[z].w;
cut(e[z-N].u, z), cut(e[z-N].v, z);
}
}
if(!flag) return;
T[id+N].mx = T[id+N].w = e[id].w, T[id+N].id = id+N;
link(x, id+N); link(y, id+N);
}
}
LL ans[N];
int main() {
init();
int k = 0;
for(int i = 2; i < N; i++) {
for(int j = 1; j*j <= i; j++) if(i%j == 0) {
e[++k] = {i, j, -j};
lct::merge(i, j, k);
if(j>1 && i/j!=j) {
e[++k] = {i, i/j, -i/j};
lct::merge(i, i/j, k);
}
}
ans[i] = -now;
}
int n; while(~scanf("%d", &n)) printf("%lld\n", ans[n]);
return 0;
}