并查集
好写,跑得快,操作不可逆
普通并查集
例题: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;
}