GCD Counting

Yorg發表於2024-10-16

演算法

\(\mathcal{O}(n \log n)\) 演算法, \(95pts\)

觀察題目,發現題目要求我們求 \(\gcd\) 不等於 \(1\) 的一條最長鏈
考慮將每個數分解質因數
對於每一個 \(1 \sim k\) 中的質數, 將所有含有這個質因子的數加入一顆虛樹, 求最長鏈即可, 經過嘗試發現 \(k = 700\) 時即可透過
可以用並查集維護連通塊加速搜尋, 時間複雜度中的 \(\log n\) 就是小常數並查集
但是這樣子無法顧及到公因數很大的情況, 可以用 \(\rm{map}\) 維護要處理的質因數, 然後再來計算
但是我不想寫了

程式碼

#include <bits/stdc++.h>
#define int long long
const int MAXN = 2e5 + 20;

int n;
int Val[MAXN];

struct node
{
    int to, next;
} Edge[MAXN << 1];
int head[MAXN];
int cnt = 0;

void init()
{
    for (int i = 1; i <= n; i++)
    {
        head[i] = -1;
    }
}

void addedge(int u, int v)
{
    Edge[++cnt].to = v;
    Edge[cnt].next = head[u];
    head[u] = cnt;
}

int Prime[MAXN];
int Vis[MAXN];
int Prime_cnt = 0;
void Euler(int x)
{
    memset(Vis, false, sizeof(Vis));
    memset(Prime, 0, sizeof(Prime));

    for (int i = 2; i <= x; i++)
    {
        if(!Vis[i])
            Prime[++Prime_cnt] = i;
        for (int j = 1; j <= Prime_cnt && i * Prime[j] <= x; j++)
        {
            Vis[i * Prime[j]] = true;
            if(i % Prime[j])
                break;
        }
    }
}

bool CanUse[MAXN];
bool vis[MAXN];
int dist[MAXN];

class Union_Set
{
    private:

    public:
        int fa[MAXN];
        void init()
        {
            for (int i = 1; i <= n; i++)
            {
                fa[i] = i;
            }
        }

        int find(int x)
        {
            return fa[x] = (fa[x] == x) ? fa[x] : find(fa[x]);
        }

        void merge(int u, int v)
        {
            int fa_u = find(u);
            int fa_v = find(v);
            fa[fa_u] = fa_v;
        }
} US;

void dfs1(int Now, int fa, int d)
{
    dist[Now] = d;
    vis[Now] = true;
    for (int i = head[Now]; ~i; i = Edge[i].next)
    {
        int Nowto = Edge[i].to;
        if (CanUse[Nowto] && Nowto != fa)
        {
            US.merge(Now, Nowto);
            dfs1(Nowto, Now, d + 1);
        }
    }
}

std::vector<int> Conect[MAXN];
int Conect_cnt = 0;
int Map[MAXN];

int Find_Longest_Road()
{
    memset(vis, false, sizeof(vis));
    US.init();
    for (int i = 1; i <= n; i++)
    {
        if (!vis[i] && CanUse[i])
        {
            dfs1(i, -1, 1);
        }
    }

    memset(vis, false, sizeof(vis));
    for (int i = 1; i <= Conect_cnt; i++)
    {
        Conect[i].clear();
    }
    Conect_cnt = 0;
    for (int i = 1; i <= n; i++)
    {
        if(!CanUse[i]) continue;
        if(!vis[US.find(i)])
        {
            vis[US.find(i)] = true;
            Conect[++Conect_cnt].push_back(i);
            Map[US.find(i)] = Conect_cnt;
        }else{
            Conect[Map[US.find(i)]].push_back(i);
        }
    }

    int Ans = 0;
    for (int i = 1; i <= Conect_cnt; i++)
    {
        int rt = 0;
        for (int j = 0; j < Conect[i].size(); j++)
        {
            if(dist[rt] < dist[Conect[i][j]])
            {
                rt = Conect[i][j];
            }
        }
        dfs1(rt, -1, 1);

        for (int j = 0; j < Conect[i].size(); j++)
        {
            Ans = std::max(Ans, dist[Conect[i][j]]);
        }
    }

    return Ans;
}

signed main()
{
    freopen("counting2.in", "r", stdin);
    //freopen("counting.out", "w", stdout);

    /*讀入 + 建樹*/
    scanf("%lld", &n);
    for (int i = 1; i <= n; i++)
        scanf("%lld", &Val[i]);

    init();
    for (int i = 1; i <= n - 1; i++)
    {
        int u, v;
        scanf("%lld %lld", &u, &v);

        addedge(u, v);
        addedge(v, u);
    }

    /*質數篩*/
    Euler(25);

    int Ans = 0;
    for (int i = 1; i <= Prime_cnt; i++)
    {
        int Now_Gcd = Prime[i];
        for (int j = 1; j <= n; j++)
        {
            if(Val[j] % Now_Gcd == 0)
            {
                CanUse[j] = true;
            }else{
                CanUse[j] = false;
            }
        }

        Ans = std::max(Ans, Find_Longest_Road());
    }

    for (int i = 1; i <= n; i++)
    {
        if(Val[i] != 1)
        {
            printf("%lld", Ans);
            return 0;
        }
    }
    printf("0");

    return 0;
}

/*
3
2 3 4
1 2
2 3

1
*/

/*
3
2 3 4
1 3
2 3

2
*/

/*
3
1 1 1
1 2
2 3

0
*/

總結

看到 \(\gcd\) 就要想分解質因數, 可以列舉 \(\gcd\) 的值從而解決問題

每道題目不一定要用一次操作進行完, 可以按照統一邏輯進行多次操作

\(\mathcal{O}(n)\) 演算法

觀察到這道題很像 樹形 dp
於是去學了一下

容易發現 \(2 \times 10^5\) 內含有最多不同質因數的數量不超過 \(10\), 想到對每個數求質因數, 然後進行對含有相同質因數的進行轉移
之前的 \(\mathcal{O}(n \log n)\) 演算法需要找連通塊之類的, 常數也不小, 而這一次我們想辦法只在原圖上進行操作, 略去了分圖的時間複雜度 (類似於 分層圖 \(\rightarrow\) dp 加一維)