[COCI2021-2022#4] Šarenlist

Yorg發表於2024-11-28

演算法

首先, 合法路徑上至少有兩條邊顏色不同, 我們考慮正難則反, 統計不合法的數量, 那麼合法數量就等於總可能數 \(-\) 不可能數

中間轉化的部分在這裡寫沒有什麼意義, 跳轉至 Luogu 題解區

那麼我們的問題轉化成, 如何計算多條路徑中, 某些路徑不合法的情況數

首先我們需要考慮, 對於每條路徑, 我們考慮將其縮成一個點, 方便計算, 因為只要選擇了這一個點的顏色, 其他的都確定了 (這裡是不合法情況)

這個我們用並查集處理, 在找每條路徑時, 記錄一下即可

在列舉第 \(i\) 條路徑顏色相同並累加上對應情況數時,我們使其它邊顏色任選,就會出現其它指定的路徑顏色也相同的情況,也就是說,在計算第 \(i\) 種情況時,也會算上一部分 \(j\) 的情況( \(W_i\)\(W_j\) 有交集),或者說,\(W_i\) 實際上是 至少\(i\) 條路徑顏色相同的情況

關於如何處理路徑不合法的方案數重複計算這一問題, 我們考慮使用容斥原理

使用狀壓的 \(\rm{trick}\)

程式碼

#include <bits/stdc++.h>

#define int long long
const int MOD = 1e9 + 7;
const int MAXN = 65;

int n, m, k;

int kpow[MAXN];
int Ans = 0;

std::map<std::pair<int, int>, int> mp;
std::vector<int> e[MAXN], Past[MAXN];

struct DSU_struct
{
    int fa[MAXN];
    int getfa(int x) { return fa[x] == x ? x : fa[x] = getfa(fa[x]); }
    void fa_init() { for (int i = 1; i <= n; i++) fa[i] = i; }
} DSU;

bool dfs(int Now, int to, int fa, int num)
{
    if (Now == to)
        return true;

    for (auto i : e[Now])
    {
        if (i == fa)
            continue;
        if (dfs(i, to, Now, num))
        {
            Past[num].push_back(mp[{Now, i}]);
            return true;
        }
    }
    return false;
}

int calc(int x)
{
    DSU.fa_init();

    int cnt = 0;
    for (int i = 1; i <= m; i++)
        if (x & (1 << (i - 1)))
            for (auto j : Past[i])
                DSU.fa[DSU.getfa(j)] = DSU.getfa(Past[i][0]);
    for (int i = 1; i <= n - 1; i++)
        cnt += (DSU.fa[i] == i);
    return cnt;
}

void init() {
    kpow[0] = 1;
    for (int i = 1; i <= n; i++)
        kpow[i] = kpow[i - 1] * k % MOD;
}

signed main()
{
    scanf("%lld %lld %lld", &n, &m, &k);

    init();

    for (int i = 1; i <= n - 1; i++) {
        int u, v;
        scanf("%lld %lld", &u, &v);
        mp[{u, v}] = mp[{v, u}] = i;
        e[u].push_back(v);
        e[v].push_back(u);
    }

    /*計算簡單路徑長度*/
    for (int i = 1; i <= m; i++) {
        int u, v;
        scanf("%d %d", &u, &v);
        dfs(u, v, u, i);
    }


    for (int i = 0; i < (1 << m); i++) {
        Ans += ((__builtin_popcount(i) & 1) ? -1 : 1) * kpow[calc(i)] + MOD;
        Ans %= MOD;
    }

    printf("%lld", Ans);
}

總結

正難則反

善於轉化問題, 善於利用資料結構