kuangbin专题五并查集总结

之前只知道简单并查集,做了才学会了带权并查集。感觉很开心。。
D - How Many Answers Are Wrong
带权并查集的模板题,不过这题也有需要注意的地方。
1,sum[A]的意义是从A到rootA(rootA<=A)的和,关键点是区间前开后闭(可以想一想)。
2,由上一点也可以得出,如果想求从A到B的sum,那么需要把A–。
代码如下:

#include<iostream>
#include<stdio.h>
#include<cstring>
using namespace std;
const int maxn = 2e5 + 5;
int f[maxn], sum[maxn];
int find(int a)
{
    int pre = f[a];
    if (a != f[a])
    {
        f[a] = find(f[a]);
        sum[a] += sum[pre];
    }
    return f[a];
}


int main()
{
    int N, M;
    while (~scanf("%d %d",&N,&M))
    {
        int A, B, S;
        int ans = 0;
        for (int i = 0;i <= N;i++)f[i] = i, sum[i] = 0;
        for (int i = 1;i <= M;i++)
        {
            scanf("%d %d %d", &A, &B, &S);
            A--;
            int x = find(A), y = find(B);
            if (x != y)
            {
                f[y] = x;
                sum[y] = sum[A] + S - sum[B];
            }
            else
            {
                if (sum[B] - sum[A] != S)
                    ans++;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

E - 食物链
经典的带权并查集题,据说会做这道题大部分并查集都可以秒了(雾。。)
一开始理解错题意了,没明白题目这句话,A吃B,B吃C,C吃A。每个动物都是A,B,C中的一种。
所以实际上动物的被吃关系就组成了一个环。
我们现在弄一个结构体.

struct node
{
    int parent, relation;   //relation =0 similar
                            //=1 parent eat this
                            //=2 this eat parent. 
                            //so it like vector x->rootx
    node(int _p,int _r):parent(_p),relation(_r){}
    node(){}
}nodes[maxn];

relation代表当前这个动物和他祖先的关系(带权并查集的relation大多数都是描述当前点和他祖先的关系,只要找到这个关系的数学表达式就能用带权并查集!)。
当relation=0时,代表是同类。
当relation=1时,代表祖先吃当前动物。
当relation=2时,代表当前动物吃祖先。
现在,我们设向量 AB A B → 代表A和B的关系。
当| AB A B → |=0时,代表A和B是同类。
| AB A B → |=1时,代表B吃A。
| AB A B → |=2时,代表A吃B。
那么容易发现结构体的relation其实相当于| AROOTA A R O O T A → |向量。
我们现在来解决find中的路径压缩的relation。
a的roota在路径压缩过程中先变成了roota2,那么现在a的relation就需要指向roota2了。通过向量我们可以很容易得到:
a->roota2=a->roota+roota->roota2.
所以它的值也就更新成如代码中所写的值了。
再看Union。
其实思考的方式仍然靠向量,唯一需要注意的地方是向量的方向不能弄反了!
我们现在先来看输入 d x y。
当d=1时,x和y是同类
当d=2时,x吃y。
所以我们把d减1。那么d的值就是代表y->x的值。
再回到Union中。
rooty的祖先先更新成rootx
那么rooty->rootx=rooty->y+y->x+x->rootx。
判断假话当然也是根据向量,相信读者根据上面的想法也能理解判断的条件,这里就不讲啦。。
代码如下:

#include<iostream>
#include<cstring>
#include<stdio.h>

using namespace std;
const int maxn = 5e4 + 5;
struct node
{
    int parent, relation;   //relation =0 similar
                            //=1 parent eat this
                            //=2 this eat parent. 
                            //so it like vector x->rootx
    node(int _p,int _r):parent(_p),relation(_r){}
    node(){}
}nodes[maxn];
int find(int idx)
{
    if (nodes[idx].parent != idx)
    {
        int pre = nodes[idx].parent;
        nodes[idx].parent = find(nodes[idx].parent);
        nodes[idx].relation = (nodes[idx].relation + nodes[pre].relation) % 3;
    }
    return nodes[idx].parent;
}
void Union(int x,int y,int rootx, int rooty, int val)
{
    nodes[rooty].parent = rootx;
    nodes[rooty].relation = (3 - nodes[y].relation + val - 1+nodes[x].relation) % 3;
}
int main()
{
    int N, K;
    scanf("%d %d", &N, &K);
    for (int i = 1;i <= N;i++)
        nodes[i].parent = i, nodes[i].relation = 0;

    int opt, x, y, rootx, rooty, ans = 0;
    for (int i = 1;i <= K;i++)
    {
        scanf("%d %d %d", &opt, &x, &y);
        if (x > N || y > N) { ans++;continue; }
        else if (opt == 2 && x == y) { ans++;continue; }
        rootx = find(x);
        rooty = find(y);
        if (rootx != rooty)
        {
            Union(x, y, rootx, rooty, opt );
        }
        else
        {
            if ((nodes[y].relation + 3 - nodes[x].relation) % 3 != opt - 1)
                ans++;
        }
    }
    cout << ans << endl;
}

F - True Liars
目前为止第一道,并查集和其他考点一起运用的好题。然而考点一混博主就没思路了。。。老感觉算法搞清楚了,却还是做不出题目来。。
回到题目上来,上一题我们就明白了一个道理,带权并查集的relation实际上就是当前节点与祖先的一个关系表示。那么这题如何建立并查集关系域呢?
聪明的人可以发现,当一个人说别人是好人的话,那么这个人和他说的那个人肯定是一类人,如果说别人是坏人的话,那么这个人和他说的那个人肯定不是一类人。
(精华啊!)。
所以relation=0,代表它和祖先是同类,relation=1,代表它和祖先不是同类。
跑一遍并查集后,就可以把整个大集合分成若干和小集合。每个集合中有好人也坏人(相对而言)。那么之后就可以设dp[i][j] 为前i个集合中好人个数为j,同时可以再开一个数组route[i][j] 来记录路径。设集合个数为num,那么在跑完之后,看dp[num][p1]是不是等于1.如果等于1,就沿着路径往前跑就行了。
代码如下:

#include<iostream>
#include<vector>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 1005;
struct node
{
    int father, relation;
    node(int _f,int _r):father(_f),relation(_r){}
    node(){}
}nodes[maxn];

int find(int idx)
{
    if (nodes[idx].father != idx)
    {
        int pre = nodes[idx].father;
        nodes[idx].father = find(pre);
        nodes[idx].relation = nodes[idx].relation^nodes[pre].relation;
    }
    return nodes[idx].father;
}

void Union(int x,int y,int rootx, int rooty,int val)
{
    if (rootx < rooty)swap(rootx, rooty);
    nodes[rootx].father = rooty;
    nodes[rootx].relation = nodes[x].relation^val^nodes[y].relation;
}
vector<int>V[maxn],IDX,ans;
int number[maxn][2];
int route[maxn][maxn];
int dp[maxn][maxn];
bool vis[maxn];
int main()
{
    int n, p1, p2;
    while (~scanf("%d %d %d", &n, &p1, &p2) &&(n!=0||p1!=0||p2!=0))
    {
        memset(vis, 0, sizeof(vis));
        memset(number, 0, sizeof(number));
        memset(route, -1, sizeof(route));
        memset(dp, 0, sizeof(dp));
        for (int i = 1;i <= p1 + p2;i++)nodes[i] = node(i, 0), V[i].clear();
        IDX.clear();
        ans.clear();
        int a, b, roota, rootb;
        char s[10];
        for (int i = 1;i <= n;i++)
        {
            scanf("%d %d %s", &a, &b, &s);
            roota = find(a);
            rootb = find(b);
            if (roota != rootb)
            {
                if (s[0] == 'y')
                    Union(a,b,roota, rootb, 0);
                else
                    Union(a,b,roota, rootb, 1);
            }
        }

        for (int i = 1;i <= p1 + p2;i++)
        {
            int root = find(i);
            V[root].push_back(i);
            if (!vis[root])
                IDX.push_back(root), vis[root] = 1;
        }
        int Size = IDX.size();
        for (int i = 0;i < Size;i++)
        {
            int theidx = IDX[i];
            int thesize = V[theidx].size();
            for (int j = 0;j < thesize;j++)
            {
                int nodeidx = V[theidx][j];
                if (!nodes[nodeidx].relation)
                    number[theidx][0]++;
                else
                    number[theidx][1]++;
            }
        }

        for (int i = 0;i < Size;i++)
        {
            if (i == 0)
            {
                int theidx = IDX[0];
                int number0 = number[theidx][0], number1 = number[theidx][1];
                dp[0][number0] ++;
                route[0][number0] = 0;
                dp[0][number1] ++;
                route[0][number1] = 1;
            }
            else
            {
                int theidx = IDX[i];
                int number0 = number[theidx][0], number1 = number[theidx][1];
                for (int j = 0;j <= p1 + p2;j++)if (route[i - 1][j] >= 0)
                {
                    dp[i][j + number0] += dp[i - 1][j];
                    route[i][j + number0] = 0;
                    dp[i][j + number1] += dp[i - 1][j] ;
                    route[i][j + number1] = 1;
                }
            }
        }
        if (dp[Size - 1][p1] == 1)
        {
            int num = p1;
            int dep = Size - 1;
            while (num)
            {
                int way = route[dep][num];
                int theidx = IDX[dep];
                for (int i = 0;i < V[theidx].size();i++)
                {
                    int nodeidx = V[theidx][i];
                    if (nodes[nodeidx].relation == way)
                        ans.push_back(nodeidx);
                }
                num -= number[theidx][way];
                dep--;
            }
            sort(ans.begin(), ans.end());
            for (int i=0;i<ans.size();i++)
                printf("%d\n", ans[i]);
            puts("end");
        }
        else
            puts("no");
    }
    return 0;
}

G - Supermarket
题意大概就是每个东西有个价值和截止日期,每天能卖一个东西,问n天后能获得的最大的价值是多少。
也是看了题解才恍然大悟,先给东西按价值从大到小排序,然后用并查集的目的就是最快找到截止日期前没卖东西的那天。题目和cf的C. String Reconstruction 差不多。。
代码如下:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 10005;
int f[maxn];
typedef pair<int, int>pii;
pii nodes[maxn];
bool cmp(pii a, pii b)
{
    return a.first > b.first;
}
int find(int a) { return a == f[a] ? a : f[a] = find(f[a]); }
void Union(int x, int y)
{
    f[x] = y;
}
int main()
{
    int n;
    while (cin>>n)
    {
        for (int i = 1;i <= 10000;i++)f[i] = i;
        int p, d;
        for (int i = 1;i <= n;i++)
            scanf("%d %d", &nodes[i].first, &nodes[i].second);
        sort(nodes + 1, nodes + 1 + n, cmp);
        int ans = 0;
        for (int i = 1;i <= n;i++)
        {
            int theday = nodes[i].second;
            int root = find(theday);
            if (root > 0)
            {
                ans += nodes[i].first;
                Union(root, root - 1);
            }
        }
        cout << ans << endl;
    }
}

H - Parity game
这题思路其实和之前的差不多,然而length不大于1e9。。数组根本开不下去。所以这时候需要离散化后再进行并查集。
不是特别熟悉离散化,所以记录一下这题。

#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<stdio.h>
using namespace std;
const int maxn =5005*4 ;
int f[maxn], sum[maxn];
struct node
{
    int u, v, ones;
}nodes[maxn];
vector<int>V;

int find(int a)
{
    if (f[a] != a)
    {
        int pre = f[a];
        f[a] = find(f[a]);
        sum[a] = sum[a] ^ sum[pre];
    }
    return f[a];
}

void Union(int u, int v, int rootu, int rootv, int val)
{
    f[rootv] = rootu;
    sum[rootv] = sum[u] ^ val^sum[v];
}

int main()
{
    for (int i = 0;i < maxn;i++)f[i] = i, sum[i] = 0;
    int n;
    scanf("%d", &n);
    scanf("%d", &n);
    char s[10];
    for (int i = 0;i < n;i++)
    {
        scanf("%d %d %s", &nodes[i].u, &nodes[i].v, s);
        if (s[0] == 'e')nodes[i].ones = 0;
        else nodes[i].ones = 1;
        V.push_back(nodes[i].u);
        V.push_back(nodes[i].v);
        V.push_back(nodes[i].u - 1);
        V.push_back(nodes[i].v - 1);
    }
    sort(V.begin(), V.end());
    unique(V.begin(), V.end());
    int i;
    for ( i = 0;i < n;i++)
    {
        int uidx = lower_bound(V.begin(), V.end(), nodes[i].u - 1) - V.begin();
        int vidx = lower_bound(V.begin(), V.end(), nodes[i].v) - V.begin();
        int rootu = find(uidx);
        int rootv = find(vidx);
        if (rootu != rootv)
            Union(uidx, vidx, rootu, rootv, nodes[i].ones);
        else
        {
            if ((sum[vidx] ^ sum[uidx]) != nodes[i].ones)
                break;
        }
    }
    cout << i << endl;
}

I - Navigation Nightmare
这题比较特别的地方是要开2个关系域,一个代表水平距离,一个代表垂直距离。另外注意方向即可。

#include<iostream>
#include<vector>
#include<cstring>
#include<stdio.h>
#include<math.h>
#include<stdlib.h>
using namespace std;
const int maxn = 40005;
const int maxm = 40005;
int f[maxn], sum1[maxn], sum2[maxn];
struct query
{
    int u, v, dis;
    char s[3];
}Q[maxm];
typedef pair<int, int>pii;
vector<pii>V[maxm];

int find(int a)
{
    if (f[a] != a)
    {
        int pre = f[a];
        f[a] = find(f[a]);
        sum1[a] += sum1[pre];
        sum2[a] += sum2[pre];
    }
    return f[a];
}

void Union(int u, int v, int rootu, int rootv, int val, char *s)
{
    int tmp1, tmp2;
    if (s[0] == 'N')
        tmp1 = val, tmp2 = 0;
    else if (s[0] == 'E')
        tmp1 = 0, tmp2 = val;
    else if (s[0] == 'W')
        tmp1 = 0, tmp2 = -val;
    else
        tmp1 = -val, tmp2 = 0;
    //if (rootu < rootv)swap(rootu, rootv);
    f[rootu] = rootv;
    sum1[rootu] = -sum1[u] + tmp1 + sum1[v];
    sum2[rootu] = -sum2[u] + tmp2 + sum2[v];
}

int main()
{
    int N, M;
    scanf("%d %d", &N, &M);
    for (int i = 1;i <= N;i++)f[i] = i, sum1[i] = 0, sum2[i] = 0;
    for (int i = 1;i <= M;i++)
    {
        scanf("%d %d %d %s", &Q[i].u, &Q[i].v, &Q[i].dis, Q[i].s);
    }
    int q;
    scanf("%d", &q);
    int u, v, idx;
    while (q--)
    {
        scanf("%d %d %d", &u, &v, &idx);
        V[idx].push_back(pii(u, v));
    }
    int rootu, rootv;
    for (int i = 1;i <= M;i++)
    {
        u = Q[i].u, v = Q[i].v;
        rootu = find(u);
        rootv = find(v);
        if (rootu != rootv)Union(u, v, rootu, rootv, Q[i].dis, Q[i].s);
        int Size = V[i].size();
        for (int j = 0;j < Size;j++)
        {
            u = V[i][j].first, v = V[i][j].second;
            rootu = find(u);
            rootv = find(v);
            if (rootu != rootv)
                puts("-1");
            else
            {
                int dist1 = sum1[u] - sum1[v], dist2 = sum2[u] - sum2[v];
                int dis = abs(dist1) + abs(dist2);
                printf("%d\n", dis);
            }
        }
    }
    return 0;
}

K - Rochambeau
枚举裁判,然后并查集判断

裁判由于可以任意出,所以可能属于任意一个集合,所以有裁判参与的会合不考虑,然后并查集部分和食物链很相似。

如果某个裁判那里出现了矛盾,则记录一下在哪出问题。

然后判断是否只有一个裁判没有出现问题。如果只有一个,说明可以确定,那么就是剩下的人出问题的最大值。枚举裁判,然后并查集判断

裁判由于可以任意出,所以可能属于任意一个集合,所以有裁判参与的会合不考虑,然后并查集部分和食物链很相似。

如果某个裁判那里出现了矛盾,则记录一下在哪出问题。

然后判断是否只有一个裁判没有出现问题。如果只有一个,说明可以确定,那么就是剩下的人出问题的最大值。因为只有否定了其它所有人都不是裁判,才能确定他是裁判。

#include<iostream>
#include<cstring>
#include<stdio.h>
#include<string>
#include<algorithm>
using namespace std;
const int maxn = 505;
const int maxm = 2005;
const int inf = 0x3f3f3f3f;
int f[maxn], sum[maxn];
int find(int a)
{
    if (f[a] != a)
    {
        int pre = f[a];
        f[a] = find(f[a]);
        sum[a] = (sum[a] + sum[pre]) % 3;
    }
    return f[a];
}

void Union(int u, int v, int rootu, int rootv, int val)
{
    f[rootu] = rootv;
    sum[rootu] = (3 - sum[u] + val + sum[v]) % 3;
}
string s[maxm];
int main()
{
    int N, M;
    while (~scanf("%d %d", &N, &M))
    {

        for (int i = 0;i < M;i++)
            cin >> s[i];

        int ansnum = 0, ansidx = 0,ans;
        for (int i = 0;i < N;i++)
        {
            for (int j = 0;j < N;j++)f[j] = j, sum[j] = 0;
            //bool flag = true;
            int j;
            int tmp;
            for ( j = 0;j < M;j++)
            {
                int Size = s[j].size();
                int num, u, v, rootu, rootv;

                for (int t = 0;t < Size;t++)
                {
                    if(s[j][t]=='=')
                    {
                        num = 0;break;
                    }
                    else if (s[j][t] == '>')
                    {
                        num = 1;break;
                    }
                    else if (s[j][t] == '<')
                    {
                        num = 2;break;
                    }
                }

                if (num == 0)
                    sscanf(s[j].c_str(), "%d=%d", &u, &v);
                else if (num == 1)
                    sscanf(s[j].c_str(), "%d>%d", &u, &v);
                else sscanf(s[j].c_str(), "%d<%d", &u, &v);
                if (u == i || v == i)continue;
                rootu = find(u);
                rootv = find(v);
                if (rootu != rootv)
                    Union(u, v, rootu, rootv, num);
                else
                {
                    if ((sum[u] + 3 - sum[v]) % 3 != num)
                    {
                        tmp = j + 1;
                        break;
                    }
                }
            }
            if (j == M)
            {
                ansnum++;
                ans = i;
            }
            else
                ansidx = max(ansidx, tmp);
        }
        if (ansnum > 1)
            puts("Can not determine");
        else if (ansnum == 0)
            puts("Impossible");
        else printf("Player %d can be determined to be the judge after %d lines\n", ans, ansidx);
    }
    return 0;
}

M - 小希的迷宫
题目简单但有几个坑点。
1,不能有森林。(博主注意到了)
2,指向自己的应该也算可行。(注意到了,不过好像没这样的数据。。)
3,一开始输入0 0也算。。(很无语。。)
代码如下:

#include<iostream>
#include<stdio.h>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = 100005;
int f[maxn];
int find(int a) { return a == f[a] ? a : f[a] = find(f[a]); }
void Union(int a, int b) { if (a > b)swap(a, b);f[b] = a; }
vector<int>V;
int main()
{
    int u, v,rootu,rootv;
    while (~scanf("%d %d",&u,&v)&&(u!=-1||v!=-1))
    {
        if (u == 0 && v == 0)
        {
            puts("Yes");
            continue;
        }
        V.clear();
        for (int i = 1;i < maxn;i++)f[i] = i;
        rootu = find(u);rootv = find(v);
        V.push_back(u);
        V.push_back(v);
        Union(rootu, rootv);
        bool flag = true;
        while (~scanf("%d %d",&u,&v)&&(u!=0||v!=0))
        {
            //if (u == v)continue;
            V.push_back(u);
            V.push_back(v);
            rootu = find(u);
            rootv = find(v);
            if (rootu == rootv)
            {
                flag = false;
                puts("No");
                //break;
            }
            else
                Union(rootu, rootv);
        }
        if (flag)
        {
            sort(V.begin(), V.end());
            unique(V.begin(), V.end());
            int theroot = find(V[0]);
            for(int i=1;i<V.size();i++)
                if (find(V[i]) != theroot)
                {
                    flag = false;
                    break;
                }
            if (flag)
                puts("Yes");
            else puts("No");
        }
    }

}

N - Is It A Tree?
这题和上题差不多,但有点细节不同。
1,空树算。
2,指向自己的不算。
3,森林不算。
注意细节就好!

#include<iostream>
#include<stdio.h>
#include<cstring>
#include<set>
#include<algorithm>
using namespace std;
const int maxn = 100005;
int f[maxn];
int find(int a) { return a == f[a] ? a : f[a] = find(f[a]); }
void Union(int a, int b) { if (a > b)swap(a, b);f[b] = a; }
set<int>V;
void init()
{
    for (int i = 1;i < maxn;i++)f[i] = i;
    V.clear();
}
int main()
{
    int u, v,rootu,rootv;
    int cas = 1;
    bool flag = 1;
    init();
    while (~scanf("%d %d",&u,&v)&&(u!=-1||v!=-1))
    {

        if (u == 0 && v == 0)
        {
            if (flag)
            {
                int cnt = 0,tmp;
                for (set<int>::iterator it = V.begin();it != V.end();it++)
                {
                    if (it == V.begin())
                        tmp = find(*it), cnt++;
                    else if (tmp != find(*it))
                        cnt++;
                }
                if (cnt > 1)flag = 0;
            }
            if (flag)printf("Case %d is a tree.\n", cas++);
            else printf("Case %d is not a tree.\n", cas++);

            init();
            flag = 1;
            continue;
        }
        rootu = find(u);
        rootv = find(v);
        if (rootu == rootv)
            flag = 0;
        else
            Union(rootu, rootv);
        V.insert(u);
        V.insert(v);
    }

}

哇,做题一定要注意细节,追求1A率!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值