2020暑期训练3

并查集

好写,跑得快,操作不可逆

普通并查集

例题:P6121 [USACO16OPEN]Closing the Farm G

给一个无向图,每次去掉一个点和这个点出发的所有边,问此时图是否联通,允许离线。如果按顺序逐渐删点,每删一次搜索一次一定会T。好在可以将操作的顺序倒过来,视作逐渐增加点和边,就可以用并查集做。从后往前逐个加点,并把从新点出发的另一个顶点也存在于图中的所有边加上去(join),然后检查图中联通块个数,是一个就说明联通了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
const int INF = 0x3f3f3f3f;
const int maxn = 2e5 + 9;
inline int read()
{
    int data = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        data = (data << 3) + (data << 1) + ch - '0';
        ch = getchar();
    }
    return f * data;
}

struct dsu
{
    int s[maxn], num;
    void create()
    {
        num = 0;
        memset(s, 0, sizeof(int) * maxn);
    }
    void init(int x)
    {
        s[x] = x, num++;
    }
    int find(int x)
    {
        if (s[x] == x)
            return x;
        else
            return s[x] = find(s[x]);
    }
    void join(int u, int v)
    {
        int f_u = find(u), f_v = find(v);
        if (f_u != f_v)
            s[f_v] = f_u;
    }
};
dsu d;
vi e[maxn];
int del[maxn];
bool ans[maxn];
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("data.txt","w",stdout);
    //std::ios::sync_with_stdio(false);
    //std::cin.tie(0);
    int n = read(), m = read(), cnt = 0;
    d.create();
    for (int i = 1; i <= m; ++i)
    {
        int x = read(), y = read();
        e[x].emplace_back(y);
        e[y].emplace_back(x);
    }
    for (int i = 1; i <= n; ++i)
        del[i] = read();
    for (int i = n; i > 0; --i)
    {
        int x = del[i];
        cnt++;
        d.init(x);
        for (int j = 0; j < e[x].size(); ++j)
        {
            int y = d.s[e[x][j]];
            if (y != 0 && d.find(x) != d.find(y))
            {
                cnt--;
                d.join(x, y);
            }
        }
        if (cnt <= 1)
            ans[i] = true;
    }
    for (int i = 1; i <= n; ++i)
        puts(ans[i] ? "YES" : "NO");
    //fclose(stdin);
    //fclose(stdout);
    return 0;
}

带权并查集

例题:P1196 [NOI2002]银河英雄传说
在学带权并查集之前我以为这题是要用Splay做的,用带权并查集就省力很多。这里的带权并查集维护了每个序列总长度以及该舰队在序列中的位置,每个新的舰队接上去的时候更新总序列长度和新加的舰队位置即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
const int INF = 0x3f3f3f3f;
const int maxn = 3e4 + 9;
inline int read()
{
    int data = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        data = (data << 3) + (data << 1) + ch - '0';
        ch = getchar();
    }
    return f * data;
}
struct dsu
{
    int fa[maxn], dis[maxn], len[maxn], num;
    void create()
    {
        for (int i = 1; i < maxn; ++i)
            fa[i] = i, len[i] = 1;
        memset(dis, 0, sizeof(int) * maxn);
    }
    int find(int x)
    {
        if (fa[x] == x)
            return x;
        int father = find(fa[x]);
        dis[x] += dis[fa[x]];
        fa[x] = father;
        return father;
    }
    void join(int u, int v)
    {
        int f_u = find(u), f_v = find(v);
        if (f_u != f_v)
        {
            fa[f_u] = f_v;
            dis[f_u] += len[f_v];
            len[f_v] += len[f_u];
        }
    }
    void check(int u, int v)
    {
        if (find(u) != find(v))
            cout << -1 << '\n';
        else
            cout << abs(dis[u] - dis[v]) - 1 << '\n';
    }
};
dsu d;

int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("data.txt","w",stdout);
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    char op;
    int x, y;
    d.create();
    cin >> d.num;
    for (int i = 1; i <= d.num; ++i)
    {
        cin >> op >> x >> y;
        if (op == 'M')
            d.join(x, y);
        else
            d.check(x, y);
    }
    //fclose(stdin);
    //fclose(stdout);
    return 0;
}

扩展域并查集

例题:P2024 [NOI2001]食物链
普通并查集只能维护同类的东西,而开一个两三倍大小的并查集可以维护物品的对立关系,比如在这里把每种动物复制三份,把吃与被吃的对立关系转化掉,就可以做了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef vector<int> vi;
const int INF = 0x3f3f3f3f;
const int maxn = 1e5 + 9;
inline int read()
{
    int data = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        data = (data << 3) + (data << 1) + ch - '0';
        ch = getchar();
    }
    return f * data;
}
int ans, f[maxn * 3];
int find_fa(int x)
{
    return f[x] == x ? x : f[x] = find_fa(f[x]);
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("data.txt","w",stdout);
    //std::ios::sync_with_stdio(false);
    //std::cin.tie(0);
    int n = read(), m = read();
    for (int i = 1; i <= n * 3; ++i)
        f[i] = i;
    while (m--)
    {
        int op = read(), u = read(), v = read();
        if (u > n || v > n)
        {
            ans++;
            continue;
        }
        if (op == 1)
        {
            if (find_fa(u) == find_fa(v + n) || find_fa(v) == find_fa(u + n))
                ans++;
            else
            {
                f[find_fa(u)] = find_fa(v);
                f[find_fa(u + n)] = find_fa(v + n);
                f[find_fa(u + 2 * n)] = find_fa(v + 2 * n);
            }
        }
        else
        {
            if (find_fa(u) == find_fa(v) || find_fa(u) == find_fa(v + n))
                ans++;
            else
            {
                f[find_fa(u)] = find_fa(v + 2 * n);
                f[find_fa(u + n)] = find_fa(v);
                f[find_fa(u + 2 * n)] = find_fa(v + n);
            }
        }
    }
    cout << ans << '\n';
    //fclose(stdin);
    //fclose(stdout);
    return 0;
}

树状数组&线段树

很久没有用过这两个东西了,放两个模板上来

树状数组模板

int tree[maxn], n, m, k, x, y;
inline int lowbit(int i)
{
    return i & -i;
}
int query(int x)
{
    int ans = 0;
    for (; x; x ^= lowbit(x))
        ans = ans + tree[x];
    return ans;
}
void update(int x, int k)
{
    for (; x <= n; x += lowbit(x))
        tree[x] += k;
}
int main()
{
    scanf("%d%d", &n, &m);
    for (register int i = 1; i <= n; i++)
    {
        scanf("%d", &k);
        update(i, k);
    }
    while (m--)
    {
        scanf("%d%d%d", &k, &x, &y);
        if (k == 1)
            update(x, y);
        else
            printf("%d\n", query(y) - query(x - 1));
    }
    return 0;
}

线段树模板

long long a[maxn], seg_tree[maxn << 2], tag[maxn << 2]; 
void push_up_sum(long long p)
{
    seg_tree[p] = seg_tree[p << 1] + seg_tree[p << 1 | 1];
}
void build(long long p, long long l, long long r)
{
    tag[p] = 0;
    if (l == r)
    {
        seg_tree[p] = a[l];
        return;
    }
    long long mid = (l + r) >> 1;
    build(p << 1, l, mid);
    build(p << 1 | 1, mid + 1, r);
    push_up_sum(p);
}
void flag(long long p, long long l, long long r, long long k)
{
    tag[p] = tag[p] + k;
    seg_tree[p] = seg_tree[p] + k * (r - l + 1);
}
void push_down(long long p, long long l, long long r)
{
    long long mid = (l + r) >> 1;
    flag(p << 1, l, mid, tag[p]);
    flag(p << 1 | 1, mid + 1, r, tag[p]);
    tag[p] = 0;
}
void update(long long newl, long long newr, long long l, long long r, long long p, long long k)
{
    if (newl <= l && r <= newr)
    {
        seg_tree[p] += k * (r - l + 1);
        tag[p] += k;
        return;
    }
    push_down(p, l, r);
    long long mid = (l + r) >> 1;
    if (newl <= mid)
        update(newl, newr, l, mid, p << 1, k);
    if (newr > mid)
        update(newl, newr, mid + 1, r, p << 1 | 1, k);
    push_up_sum(p);
}
long long query(long long q_x, long long q_y, long long l, long long r, long long p)
{
    if (q_x <= l && r <= q_y)
        return seg_tree[p];
    long long ans = 0, mid = (l + r) >> 1;
    push_down(p, l, r);
    if (q_x <= mid)
        ans += query(q_x, q_y, l, mid, p << 1);
    if (q_y > mid)
        ans += query(q_x, q_y, mid + 1, r, p << 1 | 1);
    return ans;
}
int main()
{
    int n, m, k;
    long long b, c, d;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        scanf("%lld", &a[i]);
    build(1, 1, n);
    while (m--)
    {
        scanf("%d", &k);
        if (k == 1)
        {
            scanf("%lld%lld%lld", &b, &c, &d);
            update(b, c, 1, n, 1, d);
        }
        else
        {
            scanf("%lld%lld", &b, &c);
            printf("%lld\n", query(b, c, 1, n, 1));
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值