Codeforces 1733D 891C Envy+1681F Unique Occurrences(可撤销并查集)

891C Envy

题意

        给定一张图以及q个查询,输出每个查询中的边是否全部会出现在某个最小生成树里。

思路

        

首先如果只考虑一次查询,这一次查询只有一条边,那么只要用kruskal算法处理完所有边权小于这条边的边,此时如果这条边的两点已经在同一个连通块里了,那么这条边不可能在MST里,反之则一定可以在某一个MST里。

对于多次查询,每次查询多条边来说类似,先存下所有的询问,然后按照边从小到大开始遍历,如果一组询问中有一条边不符合,那么这个询问就是NO的。

用可撤销并查集来维护每次询问每种边权增加后还原的操作。

代码

#include <bits/stdc++.h>

using namespace std;
#define int unsigned long long
const int N = 5e5 + 100;
const int mod = 1000000007;
const int INF = 0x3f3f3f3f;

int n, m;
struct node {
    int u, v, w;
} a[N];

int fa[N], sz[N];
vector<pair<int &, int>> his_sz, his_fa;
map<int, vector<int>> mp[N];
vector<int> ve[N];
int ans[N];

int find(int x) {
    while (x != fa[x])
        x = fa[x];
    return fa[x];
}

void union_set(int x, int y) {
    x = find(x);
    y = find(y);
    if (x != y) {
        if (sz[x] < sz[y])
            swap(x, y);
        his_sz.emplace_back(sz[x], sz[x]);
        sz[x] += sz[y];
        his_fa.emplace_back(fa[y], fa[y]);
        fa[y] = x;
    }
}

int history() {
    return his_fa.size();
}

void roll(int h) {
    while (his_fa.size() > h) {
        his_fa.back().first = his_fa.back().second;
        his_fa.pop_back();
        his_sz.back().first = his_sz.back().second;
        his_sz.pop_back();
    }
}


void solve() {
    cin >> n >> m;
    for (int i = 1; i <= m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        a[i].u=u;
        a[i].v=v;
        a[i].w=w;
        ve[w].push_back(i);
    }
    for(int i=1;i<=n;i++){
        fa[i]=i;
        sz[i]=1;
    }
    int q;
    cin >> q;
    for (int j = 1; j <= q; j++) {
        int k;
        cin >> k;
        for (int i = 1; i <= k; i++) {
            int idx;
            cin >> idx;
            mp[a[idx].w][j].push_back(idx);
        }
    }
    for (int w = 1; w <= 500010; w++) {
        if (ve[w].empty())continue;
        for (auto x: mp[w]) {
            if (ans[x.first] == 1)continue;
            int h = history();
            for (int y: x.second) {
                if (find(a[y].u) == find(a[y].v))ans[x.first] = 1;
                union_set(a[y].u, a[y].v);
            }
            roll(h);
        }
        for (auto x: ve[w]) {
            union_set(a[x].u, a[x].v);
        }
    }
    for (int i = 1; i <= q; i++) {
        if(ans[i]==1)cout<<"NO"<<'\n';
        else cout<<"YES"<<'\n';
    }
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
//	cin>>t;
    while (t--) solve();

    return 0;
}

Unique Occurrences

题意

给定一课带边权的树,对于一条从u到v的简单路径, f(u,v) 表示路径上只出现一次的边权的数量,输出所有 f(u,v) 的和。

思路

对于所有边权为 w 的边,它们对于答案的贡献就是只经过一次边权为 w 的路径的数量。因此可以把树中所有边权为 w 的边删除,剩余了一些连通块,这些连通块内没有边权为 w 的边,连通块之间原本是有一条边权为 w 的边,这样就比较容易计算了, w 对于答案的贡献就是所有连通块的大小两两相乘的和。

实现用简单的分治完成

代码

#include <bits/stdc++.h>

using namespace std;
#define int unsigned long long
const int N = 5e5 + 100;
const int mod = 1000000007;
const int INF = 0x3f3f3f3f;

vector<pair<int,int>>to[N];

int fa[N], sz[N];
vector<pair<int &, int>> his_sz, his_fa;


int find(int x) {
    while (x != fa[x])
        x = fa[x];
    return fa[x];
}

void union_set(int x, int y) {
    x = find(x);
    y = find(y);
    if (x != y) {
        if (sz[x] < sz[y])
            swap(x, y);
        his_sz.emplace_back(sz[x], sz[x]);
        sz[x] += sz[y];
        his_fa.emplace_back(fa[y], fa[y]);
        fa[y] = x;
    }
}

int history() {
    return his_fa.size();
}

void roll(int h) {
    while (his_fa.size() > h) {
        his_fa.back().first = his_fa.back().second;
        his_fa.pop_back();
        his_sz.back().first = his_sz.back().second;
        his_sz.pop_back();
    }
}

int dfs(int l,int r){
    if(l==r){
        int res=0;
        for(auto p:to[l]){
            res+=sz[find(p.first)]*sz[find(p.second)];
        }
        return res;
    }
    int res=0;
    int mid=(l+r)/2;
    int h=history();
    for(int i=l;i<=mid;i++){
        for(auto p:to[i]){
            union_set(p.first,p.second);
        }
    }
    res+=dfs(mid+1,r);
    roll(h);


    h=history();
    for(int i=mid+1;i<=r;i++){
        for(auto p:to[i]){
            union_set(p.first,p.second);
        }
    }
    res+=dfs(l,mid);
    roll(h);
    
    return res;
}


void solve(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        fa[i]=i;
        sz[i]=1;
        if(i==n)continue;
        int u,v,w;
        cin>>u>>v>>w;
        to[w].emplace_back(u,v);
    }
    cout<<dfs(1,n);
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t = 1;
//	cin>>t;
    while (t--) solve();

    return 0;
}

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

心刍

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值