1. 程式人生 > 實用技巧 >牛客挑戰賽42 B 樹上啟發式合併

牛客挑戰賽42 B 樹上啟發式合併

題目

(https://ac.nowcoder.com/acm/contest/6944/B)

題目描述

小睿睿和小熙熙是很好的朋友,他們的學校有n個教室,教室間有n-1條路徑且任意教室互相聯通,每個教室給他們帶來的愉悅值為val[i],
每天他們會選擇在兩個不同的教室(i,j)間的簡單路徑上秀恩愛,並給在lca(i,j)教室的人帶來gcd(val[i],val[j])的傷害。
每個教室裡的單身狗們想知道:能給他們帶來最大傷害和對應的無序點對數有多少個(對於葉子結點,最大傷害及對應的無序點對個數為0)
無序點對:(i,j)與(j,i)視作同一對,gcd(a,b):a與b的最大公因數

輸入描述:

第1行1個整數n,表示教室個數

第2至n行,每行兩個整數a,b,表示a,b間有一條邊

第n+1行,共n個整數,表示第i個教室的權值為val[i]

輸出描述:

共n行,每行2個整數,分別表示第i個點受到的最大傷害和對應的無序點對數

輸入

8
1 2
1 3
1 8
2 4
2 5
3 6
3 7
9 9 9 12 12 12 3 9

輸出

12 2
12 1
3 3
0 0
0 0
0 0
0 0
0 0

備註:

樹以1號教室為根
對於50%的資料,n≤1000且樹的形態隨機生成

另有20%的資料,樹的形態為一條鏈

對於100%的資料,n,val[i]≤100000

思路一 啟發式合併

我們考慮樹上啟發式合併,那麼對於一個樹,我們並不知道,他和存在的數誰的gcd最大。
那麼我們把數拆成他所有的因子。這樣我們就可以考慮所有的貢獻了,因為可能的gcd一個是他的因子。

現在就可以啟發式合併了,我剛開始考慮輕重鏈啟發式合併,發現一個問題,子樹間每個因子的對數和,必須記錄。

而這個是沒有辦法記錄的,後了考慮SET的合併,用一個mp記錄了可以。複雜度多個log,可以過了,

後面發現看題解發現

其實只記錄當前gcd的最大值和個數就可以了。如果現在的最大gcd為x,對數為y。合併這棵子數得到<a, b>
\(a<x\) 因為要先滿足gcd最大,所以這個b雖然沒有考慮之前的子樹貢獻,但是這個一定用不到。
\(a==x\) 那麼y記錄了之前的貢獻,b記錄這看子樹新產生的貢獻,那麼y+=b。就可以了。對數是正確的。
\(a>x\) 那麼y在之前一定沒有在兩棵子數出現過,不然最大的gcd不是x。所以y是正確的,用<x, y>替換<a, b>就可以了。

所以記錄最大的gcd和對數就可以了。

思路二 線段樹合併。

和上面的思路差不多,只是用線段樹來替換map。那麼在合併的同時,計算貢獻。

#pragma GCC optimize(3, "Ofast", "inline")
//#pragma GCC target("avx,avx2,fma")
//#pragma GCC optimization ("unroll-loops")

#include <bits/stdc++.h>
#define rint register int
#define LL long long
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)

using namespace std;

char buf[1<<20],*p1=buf,*p2=buf;
inline LL read() {
    char c=getchar();
    LL x=0,f=1;
    while(c<'0'||c>'9') {
        if(c=='-')
            f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9') {
        x=x*10+c-'0';
        c=getchar();
    }
    return x*f;
}

inline void print(LL x) {
    if (x < 0) {
        putchar('-');
        x = -x;
    }
    if (x >= 10)
        print(x / 10);
    putchar(x % 10 + '0');
}

struct edge {
    int to, nex;
} e[100010 << 1];
int head[100010], tot;

void add(int x, int y) {
    e[++tot].to = y;
    e[tot].nex = head[x];
    head[x] = tot;
}

int b[100010];
vector<int> v[100010];
pair<int, int> ans[100010];
map<int, LL, greater<int> > mp[100010];
pair<int, LL> sum[100010];

void DFS(int u, int fa) {

    for(auto x: v[b[u]]) {
        mp[u][x]++;
    }

    for(int i = head[u]; i; i = e[i].nex) {
        int to=e[i].to;
        if(to==fa) continue;
        DFS(to, u);
        if(mp[u].size()<mp[to].size()) { //啟發式貪心
            swap(mp[u], mp[to]);
        }
        for(auto x: mp[to]) {
            int s1=x.first;
            LL s2=x.second;
            if(mp[u].count(s1)){//計算新貢獻
                if(s1==sum[u].first){
                    sum[u].second+=s2*mp[u][s1];
                }
                if(s1>sum[u].first){
                    sum[u]={s1, s2*mp[u][s1]};
                }
                break;
            }
        }

        for(auto x: mp[to]) {//合併子鏈
            int s1=x.first;
            LL s2=x.second;
            mp[u][s1]+=s2;
        }
    }
}

int main() {
    int n, x, y;
    n=read();

    for(int i=1; i<n; i++) {
        x=read(), y=read();
        add(x, y);
        add(y, x);
    }
    for(int i=1; i<=n; i++) {
        b[i]=read();
    }
    for(int i=1; i<=n; i++) {
        if(v[b[i]].size()==0) {
            int sz=sqrt(b[i]);
            for(int k=1; k<=sz; k++) {
                if(b[i]%k==0) {
                    v[b[i]].push_back(k);
                    if(k*k!=b[i]) {
                        v[b[i]].push_back(b[i]/k);
                    }
                }
            }
        }
    }

    DFS(1, 0);

    for(int i=1; i<=n; i++) {
        print(sum[i].first), printf(" "), print(sum[i].second), printf("\n");
    }

    return 0;
}