Balance of the Force【2018 CCPC Final B】【枚举+种类并查集+线段树】

209 篇文章 2 订阅
106 篇文章 0 订阅

2018 China Collegiate Programming Contest Final (CCPC-Final 2018) 题目链接


  这道题的思维当真是一步一步的去接近答案,想了许久。

一、种类并查集划分关系进集合

  首先,看到那个矛盾,第一想到的就是种类并查集,当然,因为这道题不是强制在线,所以当然也是可以直接用复杂度O(N)的二分图来完成这个处理。我们将这么多互相有关系的点,划分成了多个集合,每个集合都存在着一个(因为集合中只有一个点)、或者两个阵营。

  其次,不难想到,把每个并查集的最大值和最小值处理出来,但是因为是哪个阵营还不能确定,假如跟节点选择0号阵营,那么就是其他各点都是对应的阵营;假如根节点选择1号阵营,那么其余各节点就是反选,如此维护出来每个并查集2种状态分别的最大和最小值。

二、枚举最小值(最大值也是可以的)

  在处理完这个并查集之后,我们把问题拆解出来(分而治之),我们可以先看作对于一群集合,我们知道最大值和最小值(是每个集合的最大和最小值),现在对这么一群无序的区间段,我们很难去操作,但是假如对其中一维变得有序,就可以去查另一维了,所以,这里的处理方式是对于每个集合的最小值,我们按照最小值降序排序。

  然后怎么去找到这样的答案呢?那么就是需要去控制最大值的问题了,我们把最大值放进数据结构中去维护一下,我们一一遍历最小值,直到有足够的点放进去的时候,就可以计算答案了,答案就是此时数据结构中的“最大值”的最大值,再去减去此时枚举到的最小值。

  这里面的“最大值”指的是对应集合(因为实际中是有两个等集合的)中的{最小值,最大值},并且假如这两个都要放进数据结构的话,我们去取更小的,因为这样会更接近下限,使得答案更小。

三、数据结构维护最值(线段树)

  这里只需要去维护一下最大值即可,我们把上面处理出来的,维护最大的“最大值”即可。

 

总的捋一下:

  • 先把关系对立的进行处理,我用了种类并查集,然后放入集合中去即可;
  • 根据每个并查集的阵营,我们罗列0、1阵营,分放不同集合;
  • 现在处理出来两倍于并查集数量的集合群;
  • 对于所有的集合,我们按照最小值降序排列;
  • 从前往后枚举最小值,然后把其最大值放入数据结构中维护;
  • 如果是相同并查集中的集合,我们放入数据结构中的,应当是最小的“最大值”;
  • 如果是不同并查集中的集合,我们记录数量,当放进去的数量等同于并查集的数量的时候,就需要计算答案了(都要算)。

 

My Code:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
#define INF 0x3f3f3f3f
#define HalF (l + r)>>1
#define lsn rt<<1
#define rsn rt<<1|1
#define Lson lsn, l, mid
#define Rson rsn, mid+1, r
#define QL Lson, ql, qr
#define QR Rson, ql, qr
#define myself rt, l, r
using namespace std;
typedef unsigned long long ull;
typedef unsigned int uit;
typedef long long ll;
const int maxN = 2e5 + 7, IIF = 2e9 + 7;
int N, M, root[maxN], team[maxN], cnt, vis[maxN], _UP;
struct Point
{
    int x, y;
    Point(int a=0, int b=0):x(a), y(b) {}
}a[maxN];
int fid(int x)
{
    if(x == root[x]) return x;
    int tmp = root[x];
    root[x] = fid(root[x]);
    team[x] = (team[x] + team[tmp]) & 1;
    return root[x];
}
struct node   //0是原态,1是翻转态
{
    int minn, maxx, id;
    node(int a=INF, int b=-INF, int c=0):minn(a), maxx(b), id(c) {}
    friend bool operator < (node e1, node e2) { return e1.minn == e2.minn ? e1.maxx < e2.maxx : e1.minn > e2.minn; }
}t[maxN<<1];
int tree[maxN<<2], in_tree[maxN], in_maxx[maxN];
void buildTree(int rt, int l, int r)
{
    tree[rt] = -INF;
    if(l == r) return;
    int mid = HalF;
    buildTree(Lson); buildTree(Rson);
}
inline void pushup(int rt) { tree[rt] = max(tree[lsn], tree[rsn]); }
void update(int rt, int l, int r, int qx, int val)
{
    if(l == r) { tree[rt] = val; return; }
    int mid = HalF;
    if(qx <= mid) update(Lson, qx, val);
    else update(Rson, qx, val);
    pushup(rt);
}
inline void init()
{
    cnt = _UP = 0;
    for(int i=1; i<=N; i++)
    {
        root[i] = i;
        team[i] = 0;
        vis[i] = 0;
    }
}
int main()
{
    int T; scanf("%d", &T);
    for(int Cas=1; Cas<=T; Cas++)
    {
        scanf("%d%d", &N, &M);
        init();
        bool flag = true;
        for(int i=1, u, v, fa_u, fa_v; i<=M; i++)
        {
            scanf("%d%d", &u, &v);
            if(!flag) continue;
            fa_u = fid(u); fa_v = fid(v);
            if(fa_u != fa_v)
            {
                root[fa_v] = fa_u;
                team[fa_v] = (team[u] + 1 - team[v]) & 1;
            }
            else if(team[u] == team[v]) flag = false;
        }
        for(int i=1, u; i<=N; i++)
        {
            scanf("%d%d", &a[i].x, &a[i].y);
            if(!flag) continue;
            u = fid(i);
            if(!vis[u])
            {
                cnt += 2;
                vis[u] = cnt - 1;
                if(!team[i])
                {
                    t[cnt - 1] = node(a[i].x, a[i].x, ++_UP);
                    t[cnt] = node(a[i].y, a[i].y, _UP);
                }
                else
                {
                    t[cnt - 1] = node(a[i].y, a[i].y, ++_UP);
                    t[cnt] = node(a[i].x, a[i].x, _UP);
                }
                in_tree[_UP] = false;
            }
            else
            {
                if(!team[i])
                {
                    t[vis[u]].minn = min(t[vis[u]].minn, a[i].x);
                    t[vis[u]].maxx = max(t[vis[u]].maxx, a[i].x);
                    t[vis[u] + 1].minn = min(t[vis[u] + 1].minn, a[i].y);
                    t[vis[u] + 1].maxx = max(t[vis[u] + 1].maxx, a[i].y);
                }
                else
                {
                    t[vis[u]].minn = min(t[vis[u]].minn, a[i].y);
                    t[vis[u]].maxx = max(t[vis[u]].maxx, a[i].y);
                    t[vis[u] + 1].minn = min(t[vis[u] + 1].minn, a[i].x);
                    t[vis[u] + 1].maxx = max(t[vis[u] + 1].maxx, a[i].x);
                }
            }
        }
        printf("Case %d: ", Cas);
        if(!flag) { printf("IMPOSSIBLE\n"); continue; }
        buildTree(1, 1, _UP);
        sort(t + 1, t + cnt + 1);
        int sum = 0, _id = 0, ans = IIF;
        for(int i=1; i<=cnt; i++)
        {
            if(sum == _UP)
            {
                if(t[i].maxx < t[in_maxx[t[i].id]].maxx) update(1, 1, _UP, in_tree[t[i].id], t[i].maxx);
                ans = min(ans, tree[1] - t[i].minn);
            }
            else
            {
                if(!in_tree[t[i].id])
                {
                    sum++; in_tree[t[i].id] = ++_id; in_maxx[t[i].id] = i;
                    update(1, 1, _UP, _id, t[i].maxx);
                    if(sum == _UP) ans = min(ans, tree[1] - t[i].minn);
                }
                else if(t[i].maxx < t[in_maxx[t[i].id]].maxx) update(1, 1, _UP, in_tree[t[i].id], t[i].maxx);
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Wuliwuliii

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

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

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

打赏作者

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

抵扣说明:

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

余额充值