Edu36

基本情况

尝试板刷Edu的第一天,场出三题,感觉补题困难。

D. Almost Acyclic Graph

Problem - D - Codeforces

暴力的优化技巧

先考虑暴力,对每一条边进行删边,然后跑一遍判环。但是 \(m \leq 10^5\)\(O(m^2)\) 肯定超时。

我们从拓扑排序判环的角度出发,拓扑排序通过减少一个的入度来模拟删边,而该题的点数 \(n\leq500\)

所以直接枚举哪个点入度减一会无环即可。

void solve() {
    int n, m;
    std::cin >> n >> m;
    std::vector edge(n, std::vector<int>());
    std::vector<int> ind(n);
    for (int i = 0, u, v; i < m; i++) {
        std::cin >> u >> v;
        --u, --v;
        edge[u].push_back(v);
        ind[v]++;
    }
    auto save(ind);
    for (int i = 0; i < n; i++) {
        ind = save;
        if (ind[i] == 0) continue;
        ind[i]--;
        std::queue<int> q;
        for (int i = 0; i < n; i++) {
            if (ind[i] == 0) {
                q.push(i);
            }
        }
        int cnt(0);
        while(not q.empty()) {
            int fr(q.front()); q.pop(); ++cnt;
            for (auto& to : edge[fr]) {
                if (--ind[to] == 0) {
                    q.push(to);
                }
            }
        }
        if (cnt == n) {YES; return ;}//如果所有点能构成拓扑序,说明该图无环
    }
    NO; return ;
}

E. Physical Education Lessons

Problem - E - Codeforces

没想到在这第一次遇到了珂朵莉树

珂朵莉树详解yzhang

电子科技大学【ACM】Chtholly Tree (珂朵莉树)

板子

Willem, Chtholly and Seniorious

struct node {//维护三元组[l, r, val]表示该区间内元素相同
    int l, r;
    mutable i64 val;
    node(int L, int R = -1, i64 Val = 0) {
        l = L, r = R, val = Val;
    }
    bool operator<(const node &a) const {
        return l < a.l;
    }
};

std::set<node> S;
i64 a[100005], n, m, seed, vMax;

i64 rnd() {
    i64 ret = seed;
    seed = (seed * 7 + 13) % MOD;
    return ret;
}

i64 fpow(i64 x, i64 y, i64 mod) {
    i64 ans = 1;
    x %= mod;
    while (y)
    {
        if (y & 1) {
            ans = ans * x % mod;
        }
        x = x * x % mod;
        y >>= 1;
    }
    return ans;
}

auto split(int pos) {// 目的是把pos所在的点分离出来,珂朵莉树的核心
    auto it = S.lower_bound(node(pos));
    if (it != S.end() && it->l == pos) {
        return it;
    }
    it--;// 找到pos所在的区间
    auto[l, r, val](*it);
    S.erase(it);// 把该区间的三元组删掉
    S.insert(node(l, pos - 1, val));
    return S.insert(node(pos, r, val)).first;// 返回pos所在区间的迭代器
    // insert返回pair<iterator, bool>,所以.first
}

void assign(int l, int r, i64 val) {// 区间赋值
    auto itR = split(r + 1), itL = split(l);
    S.erase(itL, itR);
    S.insert(node(l, r, val));
}

void add(int l, int r, i64 val) {// 区间加,用左闭右开区间锁定题目需要操作的区间
    auto itR = split(r + 1), itL = split(l);
    for (auto it = itL; it != itR; it++) {
        it->val += val;// 把每个区间的val都加上
    }
}

i64 rank(int l, int r, int k) {// 求第k小,就是用最暴力的方法
    std::vector<std::pair<i64, int>> vec;
    auto itR = split(r + 1), itL = split(l);
    for (auto it = itL; it != itR; it++) {
        vec.push_back({it->val, (it->r) - (it->l) + 1});
    }
    sort(vec.begin(), vec.end());
    for (auto[x, y] : vec) {
        k -= y;
        if (k <= 0) {
            return x;
        }
    }
    return -1;
}

i64 sum(int l, int r, int x, int y) {// 求幂次和
    i64 ans = 0;
    auto itR = split(r + 1), itL = split(l);
    for (auto it = itL; it != itR; it++)
        ans = (ans + fpow(it->val, x, y) * ((it->r) - (it->l) + 1)) % y;
    return ans;
}

本题

比板子还简单,就是区间赋值的同时顺便更新总和即可

struct node {//维护三元组[l, r, val]表示该区间内元素相同
    int l, r;
    mutable i64 val;
    node(int L, int R = -1, i64 Val = 0) {
        l = L, r = R, val = Val;
    }
    bool operator<(const node &a) const {
        return l < a.l;
    }
};

std::set<node> S;

i64 sum(0);

auto split(int pos) {// 目的是把pos所在的点分离出来,珂朵莉树的核心
    auto it = S.lower_bound(node(pos));
    if (it != S.end() && it->l == pos) {
        return it;
    }
    it--;// 找到pos所在的区间
    auto[l, r, val](*it);
    S.erase(it);// 把该区间的三元组删掉
    S.insert(node(l, pos - 1, val));
    return S.insert(node(pos, r, val)).first;// 返回pos所在区间的迭代器
    // insert返回pair<iterator, bool>,所以.first
}

void assign(int l, int r, i64 val) {// 区间赋值
    auto itR = split(r + 1), itL = split(l);
    for (auto it(itL); it != itR; it++) {
        sum -= it->val * (it->r - it->l + 1);//顺便更新sum
    }
    S.erase(itL, itR);
    S.insert(node(l, r, val));
    sum += val * (r - l + 1);//顺便更新sum
}

F. Imbalance Value of a Tree

Problem - F - Codeforces

\[\sum^n_{i = 1}\sum^n_{j=1}\operatorname I(x, y) \]

其中 \(\operatorname I(x, y)\) 表示顶点 \(x\)\(y\) 的简单路径上 \(a_i\) 的最大值和最小值的差。

首先转化原式:

\[\sum^n_{i = 1}\sum^n_{i = 1}\max(i, j) - \sum^n_{i = 1}\sum^n_{i = 1}\min(i, j) \]

其中,\(\max(x, y)\) 表示顶点 \(x\)\(y\) 的简单路径中 \(a_i\) 的最大值,\(\min(x, y)\) 同理。

然后转化点权为边权,求最大时边权就是两点的最大值,最小时同理。

我们可以想到按照边权大小对边进行升序排序,从低到高对每一条边的两边 \(x, y\) 进行合并,并求出通过此边的路径数量 \(siz_x\times siz_y\),累计贡献 \(siz_x\times siz_y\times \max(a_x, a_y)\)。注意,这里边的权值为边两端节点权值的较大值

struct DSU {
    std::vector<int> f, siz;
    
    DSU() {}
    DSU(int n) {
        init(n);
    }
    
    void init(int n) {
        f.resize(n);
        std::iota(f.begin(), f.end(), 0);
        siz.assign(n, 1);
    }
    
    int find(int x) {
        while (x != f[x]) {
            x = f[x] = f[f[x]];
        }
        return x;
    }
    
    bool same(int x, int y) {
        return find(x) == find(y);
    }
    
    bool merge(int x, int y) {
        x = find(x);
        y = find(y);
        if (x == y) {
            return false;
        }
        siz[x] += siz[y];
        f[y] = x;
        return true;
    }
    
    int size(int x) {
        return siz[find(x)];
    }
};

signed main() {
    std::cin.tie(nullptr)->sync_with_stdio(false);
    int n;
    std::cin >> n;
    std::vector<int> W(n);
    for (auto& x : W) std::cin >> x;
    std::vector<std::tuple<int, int, int>> edge(n - 1);
    for (auto&[u, v, _] : edge) {
        std::cin >> u >> v;
        --u, --v;
    }
    i64 ans(0);
    auto solve = [&](bool o) {
        DSU dsu(n);
        for (auto&[u, v, w] : edge) {
            w = o ? std::min(W[u], W[v]) : std::max(W[u], W[v]);
        }
        std::sort(all(edge), [&](auto x1, auto x2) -> bool {
            return o ? std::get<2>(x1) > std::get<2>(x2) : std::get<2>(x1) < std::get<2>(x2);
        });
        for (auto&[u, v, w] : edge) {//从大到小/小到大遍历,显然先进入并查集的是对答案做出贡献的,成为祖宗
            auto add(1LL * dsu.size(u) * dsu.size(v) * w);
            ans = o ? ans - add: ans + add;
            dsu.merge(u, v);
        }
    };
    for (bool o : {false, true}) solve(o);
    std::cout << ans << '\n';
    return 0;
}
posted @ 2024-03-18 16:02  加固文明幻景  阅读(5)  评论(0编辑  收藏  举报