数据结构与算法:带权并查集

前言

这个应该早点学的!!

一、原理

比普通并查集强的是,带权并查集可以维护集合内任意两点的关系。

想象在一个一维数轴上,定义带权并查集中 a 到 b 的距离为 100,当且仅当 a 在 b 的右侧距离 100 的位置,即 a-b=100。之后,若有 c 到 d 的距离为 -20,就说明 c 在 d 左侧距离 20 的位置。此时,由于 a,b 和 c,d 不在一个集合中,所以是没法判断 a,b 和 c,d 的相对位置关系的。而若有了关系 a 到 c 距离为 -60,此时就可以合并两个集合,就可以求出任意两点的距离了。

在并查集中,维护好每个节点到头节点的距离 dis,头节点的 dis 为 0。在合并时,先查出 a 和 c 集合的代表节点 b 和 d,然后将 b 挂在 d 的下面。b 到 d 的距离就可以通过 c 到 d 的距离 disc 减去 a 到 b 的距离 disa 再加上 a 到 c 的距离得到。

在合并完以后,可以发现此时 disa 没有被修正对,这个数据就等到之后 find 做路径压缩时修正,只需要在父节点修正好的 dis 的基础上加上当前的 disa 即可。对于查询,就是 find 路径压缩后,直接用两节点的 dis 相减即可。

二、题目

1.推导部分和

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<" "<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

template<typename T>
struct Weighted_DSU{

    vector<int>father;
    vector<T>dis;

    Weighted_DSU(int n):father(n),dis(n){
        for(int i=0;i<n;i++){
            father[i]=i;
            dis[i]=0;
        }
    }

    int find(int i){
        if(i!=father[i]){
            int tmp=father[i];
            father[i]=find(tmp);
            dis[i]+=dis[tmp];
        }
        return father[i];
    }

    void merge(int l,int r,T v){
        int lf=find(l),rf=find(r);
        if(lf!=rf){
            father[lf]=rf;
            dis[lf]=v+dis[r]-dis[l];
        }
    }

    T query(int l,int r){
        if(find(l)!=find(r)){
            return INFLL;
        }
        return dis[l]-dis[r];
    }
};

void solve()
{
    int n,m,q;
    cin>>n>>m>>q;

    Weighted_DSU<ll>dsu(n+2);
    for(int i=1;i<=m;i++)
    {
        ll l,r,v;
        cin>>l>>r>>v;

        dsu.merge(l,r+1,v);
    }

    int l,r;
    while(q--)
    {
        cin>>l>>r;

        ll res=dsu.query(l,r+1);
        if(res==INFLL)
        {
            cout<<"UNKNOWN"<<endl;
        }
        else
        {
            cout<<res<<endl;
        }
    }
}

void init()
{
}

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

这里要维护的是单点的范围累加和,那么就可以考虑转化成一维数轴的线段问题。那么对于 l,r 范围的累加和,就可以认为是数轴上 l,r+1 两点的线段距离。那么就是用带权并查集维护,每次查询即可,注意 r+1 的问题。

2.狡猾的商人

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<" "<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

template<typename T>
struct Weighted_DSU{
    vector<int>father;
    vector<T>dis;

    Weighted_DSU(int n):father(n),dis(n){
        for(int i=0;i<n;i++){
            father[i]=i;
            dis[i]=0;
        }
    }

    int find(int i){
        if(i!=father[i]){
            int tmp=father[i];
            father[i]=find(tmp);
            dis[i]+=dis[tmp];
        }
        return father[i];
    }

    void merge(int l,int r,T v){
        int lf=find(l),rf=find(r);
        if(lf!=rf){
            father[lf]=rf;
            dis[lf]=v+dis[r]-dis[l];
        }
    }

    T query(int l,int r){
        if(find(l)!=find(r)){
            return INF;
        }
        return dis[l]-dis[r];
    }

    bool check(int l,int r,T v)
    {
        if(find(l)==find(r))
        {
            if(dis[l]-dis[r]!=v)
            {
                return false;
            }
        }
        return true;
    }
};

void solve()
{
    int n,m;
    cin>>n>>m;

    Weighted_DSU<ll>dsu(n+2);

    int ok=1;
    for(int i=1;i<=m;i++)
    {
        ll l,r,v;
        cin>>l>>r>>v;

        if(!dsu.check(l,r+1,v))
        {
            ok=0;
        }
        else
        {
            dsu.merge(l,r+1,v);
        }
    }
    cout<<(ok?"true":"false")<<endl;
}

void init()
{
}

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

这个和上一个题唯一不同的就是要判断矛盾。这个就只需要查当前 l,r+1 的值,如果在一个集合中,就说明当前可以确定两者的值。那么如果和给定的值对得上就不用管,对不上就说明矛盾了。

3.How Many Answers Are Wrong

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<" "<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

template<typename T>
struct Weighted_DSU{
    vector<int>father;
    vector<T>dis;

    Weighted_DSU(int n):father(n),dis(n){
        for(int i=0;i<n;i++){
            father[i]=i;
            dis[i]=0;
        }
    }

    int find(int i){
        if(i!=father[i]){
            int tmp=father[i];
            father[i]=find(tmp);
            dis[i]+=dis[tmp];
        }
        return father[i];
    }

    void merge(int l,int r,T v){
        int lf=find(l),rf=find(r);
        if(lf!=rf){
            father[lf]=rf;
            dis[lf]=v+dis[r]-dis[l];
        }
    }

    T query(int l,int r){
        if(find(l)!=find(r)){
            return INF;
        }
        return dis[l]-dis[r];
    }

    bool check(int l,int r,T v){
        if(find(l)==find(r)){
            if(dis[l]-dis[r]!=v){
                return false;
            }
        }
        return true;
    }
};

void solve()
{
    int n,m;
    while(cin>>n>>m)
    {
        int ans=0;
        Weighted_DSU<ll>dsu(n+2);
        
        for(int i=1,l,r,v;i<=m;i++)
        {
            cin>>l>>r>>v;

            if(!dsu.check(l,r+1,v))
            {
                ans++;
            }
            else
            {
                dsu.merge(l,r+1,v);
            }
        }
        cout<<ans<<endl;
    }
}

void init()
{
}

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

几乎一样的题,没啥好说的。

4.银河英雄传说

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<" "<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

template<typename T>
struct Weighted_DSU{
    vector<int>father;
    vector<T>dis;
    vector<int>size;

    Weighted_DSU(int n):father(n),dis(n),size(n){
        for(int i=0;i<n;i++){
            father[i]=i;
            dis[i]=0;
            size[i]=1;
        }
    }

    int find(int i){
        if(i!=father[i]){
            int tmp=father[i];
            father[i]=find(tmp);
            dis[i]+=dis[tmp];
        }
        return father[i];
    }

    void merge(int l,int r){
        int lf=find(l),rf=find(r);
        if(lf!=rf){
            father[lf]=rf;
            dis[lf]=size[rf];
            size[rf]+=size[lf];
        }
    }

    T query(int l,int r){
        if(find(l)!=find(r)){
            return -1;
        }
        return abs(dis[l]-dis[r])-1;
    }

    bool check(int l,int r,T v){
        if(find(l)==find(r)){
            if(dis[l]-dis[r]!=v){
                return false;
            }
        }
        return true;
    }
};

const int MAXN=30000+5;

void solve()
{
    int q;
    cin>>q;

    Weighted_DSU<int>dsu(MAXN);

    string op;
    int i,j;
    while(q--)
    {
        cin>>op>>i>>j;
        if(op=="M")
        {
            dsu.merge(i,j);
        }
        else if(op=="C")
        {
            int res=dsu.query(i,j);
            cout<<res<<endl;
        }
    }
}

void init()
{
}

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

对于合并的操作,很明显可以想到用并查集解决。那么对于查询两艘战舰的距离,在合并时就可以将排在后面战舰的 dis 设置为父亲集合的大小,之后的战舰就可以对应修正对了。

5.除法求值

cpp 复制代码
template<typename T>
struct Weighted_DSU{
    map<string,string>father;
    map<string,T>dis;

    Weighted_DSU(int n,vector<vector<string>>& equations){
        for(auto &vec:equations)
        {
            for(auto &str:vec)
            {
                father[str]=str;
                dis[str]=1.0;
            }
        }
    }

    string find(string i){
        if(father.find(i)==father.end())
        {
            return "";
        }

        if(i!=father[i]){
            string tmp=father[i];
            father[i]=find(tmp);
            dis[i]*=dis[tmp];
        }
        return father[i];
    }

    void merge(string l,string r,T v){
        string lf=find(l),rf=find(r);
        if(lf!=rf){
            father[lf]=rf;
            dis[lf]=dis[r]/dis[l]*v;
        }
    }

    T query(string l,string r){
        string lf=find(l),rf=find(r);
        if(lf==""||rf==""||lf!=rf){
            return -1;
        }
        return dis[l]/dis[r];
    }

    bool check(string l,string r,T v){
        if(find(l)==find(r)){
            if(dis[l]/dis[r]!=v){
                return false;
            }
        }
        return true;
    }
};

class Solution {
public:
    vector<double> calcEquation(
        vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) 
    {
        int n=equations.size();
        Weighted_DSU<double>dsu(n,equations);

        for(int i=0;i<n;i++)
        {
            dsu.merge(equations[i][0],equations[i][1],values[i]);
        }

        int m=queries.size();
        vector<double>ans(m);
        for(int i=0;i<m;i++)
        {
            ans[i]=dsu.query(queries[i][0],queries[i][1]);
        }
        return ans;
    }
};

这个很明显就是带权并查集维护倍数关系,那么将加减改成乘除,之后套板子直接写即可。

6.食物链

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<" "<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

template<class T>
constexpr T power(T a, ll b) {
    T res = 1;
    for (; b != 0; b /= 2, a *= a) {
        if (b & 1) {
            res *= a;
        }
    }
    return res;
}

template<int M>
struct ModInt {
public:
    constexpr ModInt() : x(0) {}

    template<typename T>
    constexpr ModInt(T x_) {
        T v = x_ % M;
        if (v < 0) {
            v += M;
        }
        x = v;
    }
 
    constexpr int val() const {
        return x;
    }

    constexpr ModInt &operator++() & {
        x++;
        if (x == M) {
            x = 0;
        }
        return *this;
    }

    constexpr ModInt operator++(int) & {
        ModInt res = *this;
        ++(*this);
        return res;
    }

    constexpr ModInt &operator--() & {
        if (x == 0) {
            x = M - 1;
        } else {
            x--;
        }
        return *this;
    }

    constexpr ModInt operator--(int) & {
        ModInt res = *this;
        --(*this);
        return res;
    }
 
    constexpr ModInt operator-() const {
        ModInt res;
        res.x = (x == 0 ? 0 : M - x);
        return res;
    }
 
    constexpr ModInt inv() const {
        return power(*this, M - 2);
    }
 
    constexpr ModInt &operator*=(const ModInt &rhs) &{
        x = ll(x) * rhs.val() % M;
        return *this;
    }
 
    constexpr ModInt &operator+=(const ModInt &rhs) &{
        x += rhs.val();
        if (x >= M) {
            x -= M;
        }
        return *this;
    }
 
    constexpr ModInt &operator-=(const ModInt &rhs) &{
        x -= rhs.val();
        if (x < 0) {
            x += M;
        }
        return *this;
    }
 
    constexpr ModInt &operator/=(const ModInt &rhs) &{
        return *this *= rhs.inv();
    }
 
    friend constexpr ModInt operator*(ModInt lhs, const ModInt &rhs) {
        lhs *= rhs;
        return lhs;
    }
 
    friend constexpr ModInt operator+(ModInt lhs, const ModInt &rhs) {
        lhs += rhs;
        return lhs;
    }
 
    friend constexpr ModInt operator-(ModInt lhs, const ModInt &rhs) {
        lhs -= rhs;
        return lhs;
    }
 
    friend constexpr ModInt operator/(ModInt lhs, const ModInt &rhs) {
        lhs /= rhs;
        return lhs;
    }
 
    friend constexpr bool operator==(ModInt lhs, const ModInt &rhs) {
        return lhs.val() == rhs.val();
    }
    
    friend constexpr bool operator<(ModInt lhs, const ModInt &rhs) {
        return lhs.val() < rhs.val();
    }
    
    friend constexpr bool operator>(ModInt lhs, const ModInt &rhs) {
        return lhs.val() > rhs.val();
    }
    
    friend constexpr bool operator<=(ModInt lhs, const ModInt &rhs) {
        return lhs.val() <= rhs.val();
    }
    
    friend constexpr bool operator>=(ModInt lhs, const ModInt &rhs) {
        return lhs.val() >= rhs.val();
    }
    
    friend constexpr bool operator!=(ModInt lhs, const ModInt &rhs) {
        return lhs.val() != rhs.val();
    }
 
    friend constexpr std::istream &operator>>(std::istream &is, ModInt &a) {
        ll i;
        is >> i;
        a = i;
        return is;
    }
 
    friend constexpr std::ostream &operator<<(std::ostream &os, const ModInt &a) {
        return os << a.val();
    }
 
private:
    int x;
};

template<int M, typename T = ModInt<M>>
struct Comb {
    vector<T> fac;
    vector<T> inv;
 
    Comb(int n) {
        fac.assign(n, 1);
        for(int i=1;i<n;i++)
        {
            fac[i]=fac[i-1]*i;
        }
        inv.assign(n, 1);
        inv[n-1]=fac[n-1].inv();
        for(int i=n-2;i>=0;i--)
        {
            inv[i]=inv[i+1]*(i+1);
        }
    }
 
    template<std::signed_integral U>
    T P(U n, U m) {
        if(n<m)
        {
            return 0;
        }
        return fac[n] * inv[n - m];
    }
 
    template<std::signed_integral U>
    T C(U n, U m) {
        if(n<m||m<0)
        {
            return 0;
        }
        return fac[n] * inv[n - m] * inv[m];
    }
};
 
//power函数切记强转成 Z !!!!!
constexpr int M = 3;
using Z = ModInt<M>;

template<typename T>
struct Weighted_DSU{
    vector<int>father;
    vector<T>dis;

    Weighted_DSU(int n):father(n),dis(n){
        for(int i=0;i<n;i++){
            father[i]=i;
            dis[i]=0;
        }
    }

    int find(int i){
        if(i!=father[i]){
            int tmp=father[i];
            father[i]=find(tmp);
            dis[i]+=dis[tmp];
        }
        return father[i];
    }

    void merge(int l,int r,T v){
        int lf=find(l),rf=find(r);
        if(lf!=rf){
            father[lf]=rf;
            dis[lf]=v+dis[r]-dis[l];
        }
    }

    T query(int l,int r){
        if(find(l)!=find(r)){
            return INF;
        }
        return dis[l]-dis[r];
    }

    bool check(int l,int r,T v){
        if(find(l)==find(r)){
            if(dis[l]-dis[r]!=v){
                return false;
            }
        }
        return true;
    }
};

void solve()
{
    int n,q;
    cin>>n>>q;

    Weighted_DSU<Z>dsu(n+1);

    int ans=0;
    for(int i=1,op,x,y;i<=q;i++)
    {
        cin>>op>>x>>y;

        if(x>n||y>n||(op==2&&x==y))
        {
            ans++;
            continue;
        }

        if(op==1)
        {
            if(!dsu.check(x,y,0))
            {
                ans++;
            }
            else
            {
                dsu.merge(x,y,0);
            }
        }
        else if(op==2)
        {
            if(!dsu.check(x,y,1))
            {
                ans++;
            }
            else
            {
                dsu.merge(x,y,1);
            }
        }
    }
    cout<<ans<<endl;
}

void init()
{
}

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

有了前三个题的练习,对于这种维护关系判断真假的问题,很容易就能想到使用带权并查集。那么对于这个循环吃的问题,还是考虑转化成取模问题。所以就只需要用 dis 维护 0,1,2 三种类型,0 表示和父节点类型相同,1 表示吃父节点,2 表示被父节点吃。此时可以发现,这个关系也可以在模 3 意义下的距离模型成立,所以拿基本公式表示即可。

7.关押罪犯

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<" "<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

struct DSU{
    vector<int>father;
    vector<int>siz;
    //自定义

    DSU(int n):father(n),siz(n){
        for(int i=0;i<n;i++){
            father[i]=i;
            siz[i]=1;
        }
    }

    int find(int i){
        if(i!=father[i]){
            father[i]=find(father[i]);
        }
        return father[i];
    }

    bool same(int x,int y){
        return find(x)==find(y);
    }

    bool merge(int x,int y){
        int fx=find(x);
        int fy=find(y);
        if(fx==fy){
            return false;
        }

        father[fx]=fy;
        siz[fy]+=siz[fx];
        
        return true;
    }
};

void solve()
{
    int n,m;
    cin>>n>>m;
    vector<array<int,3>>a(m+1);
    for(int i=1;i<=m;i++)
    {
        cin>>a[i][0]>>a[i][1]>>a[i][2];
    }

    sort(a.begin()+1,a.end(),[&](auto &x,auto &y)
    {
        return x[2]>y[2];
    });

    DSU dsu(n+1);
    vector<int>op(n+1);
    for(int i=1;i<=m;i++)
    {
        auto [x,y,v]=a[i];
        if(dsu.same(x,y))
        {
            cout<<v<<endl;
            return ;
        }

        if(op[x]==0)
        {
            op[x]=y;
        }
        else
        {
            dsu.merge(op[x],y);
        }

        if(op[y]==0)
        {
            op[y]=x;
        }
        else
        {
            dsu.merge(x,op[y]);
        }
    }
    cout<<0<<endl;
}

void init()
{
}

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

由于是要让最大的冲突值最小,那么就可以考虑从大到小贪心地考虑,每次尽量让当前的冲突不满足,这样找到的第一个不得不满足的冲突值就是答案。那么就可以维护之前每个人 x 发生冲突的对象 y,对于后续的冲突 (x,z),就是让 z 和 y 处于一个监狱即可,这个可以用普通并查集维护。

8.团伙 (Day 2)

感觉遇到这种分类问题就要考虑用并查集维护。

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<" "<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

struct DSU{
    vector<int>father;
    vector<int>siz;
    //自定义

    DSU(int n):father(n),siz(n){
        for(int i=0;i<n;i++){
            father[i]=i;
            siz[i]=1;
        }
    }

    int find(int i){
        if(i!=father[i]){
            father[i]=find(father[i]);
        }
        return father[i];
    }

    bool same(int x,int y){
        return find(x)==find(y);
    }

    bool merge(int x,int y){
        int fx=find(x);
        int fy=find(y);
        if(fx==fy){
            return false;
        }

        father[fx]=fy;
        siz[fy]+=siz[fx];
        
        return true;
    }
};

void solve()
{
    int n,m;
    cin>>n>>m;

    DSU dsu(n+1);
    vector<int>enemy(n+1);

    string op;
    int x,y;
    for(int i=1;i<=m;i++)
    {
        cin>>op>>x>>y;
        if(op=="E")
        {
            if(enemy[x]==0)
            {
                enemy[x]=y;
            }
            else
            {
                dsu.merge(enemy[x],y);
            }

            if(enemy[y]==0)
            {
                enemy[y]=x;
            }
            else
            {
                dsu.merge(x,enemy[y]);
            }
        }
        else if(op=="F")
        {
            dsu.merge(x,y);
        }
    }

    int ans=0;
    for(int i=1;i<=n;i++)
    {
        if(i==dsu.find(i))
        {
            ans++;
        }
    }
    cout<<ans<<endl;
}

void init()
{
}

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

跟上个题一样,就是最后看有多少个集合即可。

9.Exclusive-OR

傻逼杭电在输入恶心人......

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define endl '\n'
#define dbg(x) cout<<#x<<" "<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using i128=__int128;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

template<typename T>
struct Weighted_DSU{
    vector<int>father;
    vector<T>dis;
    int n;

    Weighted_DSU(int _n):n(_n),father(_n+1),dis(_n+1){
        for(int i=0;i<=n;i++){
            father[i]=i;
            dis[i]=0;
        }
    }

    int find(int i){
        if(i!=father[i]){
            int tmp=father[i];
            father[i]=find(tmp);
            dis[i]^=dis[tmp];
        }
        return father[i];
    }

    bool merge(int l,int r,T v){
        int lf=find(l),rf=find(r);
        if(lf==rf){
            if((dis[l]^dis[r])!=v){
                return false;
            }
        }else{
            if(lf==n)
            {
                swap(lf,rf);
            }
            father[lf]=rf;
            dis[lf]=v^dis[r]^dis[l];
        }
        return true;
    }

    T query(int l,int r){
        if(find(l)!=find(r)){
            return INF;
        }
        return dis[l]^dis[r];
    }
};

void solve()
{
    int t=0;
    int n,m;
    cin>>n>>m;
    while(n!=0&&m!=0)
    {
        cout<<"Case "<<(++t)<<":"<<endl;

        int ok=1;
        int cnti=0;
        Weighted_DSU<int>dsu(n);
        for(int _=1;_<=m;_++)
        {
            string op;
            cin>>op;
            if(op=="I")
            {
                string s;
                getline(cin,s);

                array<int,3>a={0};
                int cnta=0;
                for(auto ch:s)
                {
                    if(ch==' ')
                    {
                        cnta++;
                    }
                    else if('0'<=ch&&ch<='9')
                    {
                        a[cnta-1]=a[cnta-1]*10+ch-'0';
                    }
                }

                if(ok)
                {
                    int l,r,v;
                    if(cnta==2)
                    {
                        l=a[0],v=a[1],r=n;
                    }
                    else if(cnta==3)
                    {
                        l=a[0],r=a[1],v=a[2];
                    }

                    cnti++;
                    if(!dsu.merge(l,r,v))
                    {
                        ok=0;
                        cout<<"The first "<<cnti<<" facts are conflicting."<<endl;
                    }
                }
            }
            else if(op=="Q")
            {
                int len;
                cin>>len;
                vector<int>a(len+1);
                for(int i=1;i<=len;i++)
                {
                    cin>>a[i];
                }

                auto proc=[&]()->int
                {
                    int ans=0;
                    map<int,int>mp;
                    for(int i=1;i<=len;i++)
                    {
                        mp[dsu.find(a[i])]++;
                        ans^=dsu.dis[a[i]];
                    }
                    
                    for(auto [k,v]:mp)
                    {
                        if(k!=n&&v%2)
                        {
                            return -1;
                        }
                    }
                    return ans;
                };

                if(ok)
                {
                    int res=proc();
                    if(res==-1)
                    {
                        cout<<"I don't know."<<endl;
                    }
                    else
                    {
                        cout<<res<<endl;
                    }
                }
            }
        }

        cout<<endl;
        cin>>n>>m;
    }
}

void init()
{
}

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

这个题和之前不一样的是,这个题会给出一个绝对数值,不仅仅只有相对关系。那么对于这个问题,联系之前的差分约束,可以想到设置一个虚点 0,利用 v^0=v 这个性质,将绝对数值关系转化成相对关系处理。注意不能让这个虚点挂在别的节点下面!

对于查询操作,若查询的节点是偶数个,那么其 dis 的异或和就可以将这个公共的头节点消掉,所以答案就是所有 dis 的异或和。而若节点是奇数个,那么 dis 的异或和就会多一个头节点,那么就没法确定最后的答案。而对于头节点是虚点的情况,就不存在奇偶的限制了。

特殊的,此时就不需要判断所有点是否都在一个集合中了。对于涉及到的若干个集合,若各个集合单独的答案都是可以确定的,那么都异或起来就是最终答案。也就是说,只有当某个非虚点的头节点出现了奇数次,那么就是无解的。

总结

加快学习速度!!

END

相关推荐
熊猫_豆豆1 小时前
黑洞的粒子产生(霍金1975年论文)第二部分
人工智能·数学·算法·机器学习·量子力学·大学物理·黑洞
jay神1 小时前
本科深度学习需要从零开始训练模型吗?
人工智能·深度学习·算法·机器学习·计算机视觉
渡我白衣1 小时前
Acceptor模块的设计与实现
java·linux·服务器·开发语言·网络·c++·人工智能
程序员AlbertTu1 小时前
M01 | 浮点数与误差:数值计算的世界观
c++·数值运算
沫璃染墨1 小时前
《从零入门Linux系统篇(二十八):文件篇·一——Linux为什么“万物皆文件”:从C语言文件流到系统调用》
linux·运维·服务器·开发语言·c++·文件
j7~2 小时前
【C++】《C++STL 笔记:搞懂序列 / 关联容器,聊聊 set 和 map》
c++·set·map·multimap·multiset·键值对pair·序列是容器和关联式容器
Feynman’s boom2 小时前
PDF解析复盘
数据库·人工智能·算法·pdf解析
郝学胜-神的一滴3 小时前
《C++11 工程级应用01:告别冗长类型,开启简洁高效编码新时代》深度解读
开发语言·c++·算法·软件开发·系统设计
学习中.........4 小时前
Transformer 训练资源估算:以 CS336 GPT-2 XL 配置为例
人工智能·python·算法·机器学习·自然语言处理