1. 程式人生 > 其它 >CF1633E Spanning Tree Queries

CF1633E Spanning Tree Queries

CF1633E

原題連結 ←Click it

題目大意:給定一個無向帶權圖,現有\(k\)次詢問,每次詢問一個\(x\),對每次詢問,邊權的價值是\(|w_i-x|\),求出最小生成樹的價值,最終的答案是對所有的詢問結果取異或。

解題思路:我們先來回憶一下kruskal演算法,先將所有邊權升序排序,然後依次選邊新增到集合中,可以發現如果每條邊權的相對大小如果不發生變化,那麼kruskal選的邊不變。然後再來研究一下絕對值函式(沒想到就是看起來這麼簡單的東西把我難倒了),我們驚訝地發現,邊權相對大小產生變化的地方只出現在兩條直線的交點位置,邊權斜率正負的改變只出現在與x軸的交點,那麼我們的想法就非常簡單了,我們將數軸按照這些交點切分,此時每一段區間的邊權相對大小都不發生改變,我們求出每一段區間某個值的(為了方便,這裡選擇最右端的值,也就是交點的x值)最小生成樹的價值,並統計生成樹中邊權斜率為正(或者為負)的數量。最後對每次詢問都可以在\(O(log)\)

的時間複雜度中求出(二分判斷區間位置)。

參考程式碼:

int n, m;
struct Edge {
    int u, v, w;
};
vector<Edge> e;
struct DSU {
    int N;
    vector<int> pr;
    DSU(int n) : N(n) {
        pr.resize(N + 1);
        for(int i = 0; i <= N; i ++) {
            pr[i] = i;
        }
    }
    int root(int x) {
        return pr[x] == x ? x : pr[x] = root(pr[x]);
    }
    void unite(int x, int y) {
        int X = root(x), Y = root(y);
        if(X == Y) {
            return ;
        }
        pr[X] = Y;
    }
};
ll kruskal(int T, int &c) {
    //按照|w - T|排序,但是存在相等的情況要選w更小的,因為在交點左側|w - T|更小
    sort(e.begin(), e.end(), [&] (const Edge &a, Edge &b) {
        if(abs(a.w - T) != abs(b.w - T))
            return abs(a.w - T) < abs(b.w -T);
        else 
            return a.w < b.w;
    });
    DSU dsu(n);
    int cnt = 0;
    ll res = 0;
    for(auto eg : e) {
        int A = dsu.root(eg.u), B = dsu.root(eg.v);
        if(A == B) continue;
        dsu.unite(A, B);
        cnt ++;
        if(eg.w - T < 0) c ++;
        res += abs(eg.w - T);
        if(cnt == n - 1) {
            break;
        }
    }
    return res;
}
void solve() {
    cin >> n >> m;
    for(int i = 0; i < m; i ++) {
        int u, v, w;
        cin >> u >> v >> w;
        e.push_back({u, v, w});
    }
    //定義了1e9為無窮
    vector<int> pos(1, 1e9);
    for(int i = 0; i < m; i ++) {
        for(int j = i; j < m; j ++) {
    //新增交點到pos
            pos.push_back(e[i].w + e[j].w >> 1);
        }
    }
    sort(pos.begin(), pos.end());
    //去重!
    pos.resize(unique(pos.begin(), pos.end()) - pos.begin());
    vector<ll> dis, cnr;
    for(auto T : pos) {
        int c = 0;
        dis.push_back(kruskal(T, c));
        cnr.push_back(c);
    }
    int p, k, a, b, c;
    cin >> p >> k >> a >> b >> c;
    vector<int> q(k);
    for(int i = 0; i < k; i ++) {
        if(i < p) {
            cin >> q[i];
        } else {
            q[i] = (1ll * q[i - 1] * a + b) % c;
        }
    }
    ll res = 0;
    for(int i = 0; i < k; i ++) {
        int id = lower_bound(pos.begin(), pos.end(), q[i]) - pos.begin();
        // 生成樹價值偏移量的計算
        ll D = dis[id] - (cnr[id] * (pos[id] - q[i])) - (n - 1 - cnr[id]) * (q[i] - pos[id]);
        // cout << D << '\n';
        res ^= D;
    }
    cout << res << '\n';
}