【LCT】HDU5398 GCD Tree

该博客主要介绍了如何解决一个图论问题,即在每次给定一个节点数n的完全图,边权为节点标号的最大公约数的情况下,如何动态地构建最大生成树。博主提出了利用最大公约数的性质和LCT(Link-Cut Tree)数据结构来解决这个问题。在单次查询中,可以按照gcd的大小从后往前处理,而在动态查询中,注意每个节点只与它的约数相连不会影响答案,通过断开特定边并重新连接,可以用LCT维护最大生成树的状态。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值