题意:
n个城市,m条道路,问还需要多少条道路能将n个城市连起来(不必需要直接道路)
Sol:
根据m条道路,将n个城市进行合并操作,最终找出有多少个连通块,答案为联通块的数量减1.(n个城市连起来需要n-1条道路)
Code:
#include<bits/stdc++.h>
using namespace std;
struct UNF{
vector<int>p;
void init(int n)
{
for(int i = 0; i <= n + 10; ++i) {
p.push_back(i);
}
}
int find(int x)
{
if(x != p[x]) p[x] = find(p[x]);
return p[x];
}
void Union(int a, int b)
{
int pa = find(a);
int pb = find(b);
if(pa != pb)
{
p[pa] = pb;
}
}
};
int main(){
int n, m;
while(scanf("%d%d", &n, &m) != EOF)
{
if(!n) break;
UNF city;
city.init(n);
while(m -- )
{
int x, y;
scanf("%d%d", &x, &y);
city.Union(x, y);
}
int cnt = 0;
for(int i = 1; i <= n; ++i)
cnt += (city.p[i] == i);
printf("%d\n", cnt - 1);
}
return 0;
}
题意:有n个人,m个团队,团队有k个人,问0所在的团队的人数
Sol:并查集维护联通块的大小
Code:
#include <iostream>
#include <vector>
#include <cstdio>
using namespace std;
struct UNF{
vector<int>p;
vector<int>f;
void init(int n)
{
for(int i = 0; i <= n + 10; ++i) {
p.push_back(i);
f.push_back(1);
}
}
int find(int x)
{
if(x != p[x]) p[x] = find(p[x]);
return p[x];
}
void Union(int a, int b)
{
int pa = find(a);
int pb = find(b);
if(pa != pb)
{
p[pb] = pa;
f[pa] += f[pb];
}
}
};
int main(){
int n, m;
while(scanf("%d%d", &n, &m))
{
if(!n && !m) break;
UNF team;
team.init(n);
while( m -- )
{
int k, x, y;
scanf("%d", &k);
for(int i = 1; i <= k; ++i)
{
if(i == 1) {
scanf("%d", &x);
}
else {
scanf("%d", &y);
team.Union(x, y);
}
}
}
printf("%d\n", team.f[team.find(0)]);
}
return 0;
}
题意:有n个人,m次询问。
A 1 2 查询a和b是否是敌人或者是无法判断
D 1 2 a和b是敌人
Sol:
一:扩展域并查集。假设1-n为第一个集团,n+1-2*n为第二个集团。
换句话说,敌人的敌人就是朋友。所以对于a和b是敌人进行的并查集合并为,将a和b+n合并以及a+n和b合并
二:带权并查集,用d[i]表示与根节点的关系,0表示朋友,1表示敌人
Code:
#include <iostream>
#include <vector>
#include <cstdio>
using namespace std;
struct UNF{
vector<int>p;
void init(int n)
{
for(int i = 0; i <= n + 10; ++i) {
p.push_back(i);
}
}
int find(int x)
{
if(x != p[x]) p[x] = find(p[x]);
return p[x];
}
void Union(int a, int b)
{
int pa = find(a);
int pb = find(b);
if(pa != pb)
{
p[pa] = pb;
}
}
};
int main(){
int T; scanf("%d", &T);
while ( T -- ){
int n, m;
scanf("%d%d", &n, &m);
UNF gangs;
gangs.init(2 * n);
while( m -- )
{
char op[2];
int a, b;
scanf("%s%d%d", op, &a, &b);
if(*op == 'D') {
gangs.Union(a, b + n);
gangs.Union(a + n, b);
}
else {
int pa = gangs.find(a);
int pb = gangs.find(b);
if(pa == pb) {
puts("In the same gang.");
}
else if(pa == gangs.find(b + n)) {
puts("In different gangs.");
}
else {
puts("Not sure yet.");
}
}
}
}
return 0;
}
题意:有一个食物链,A->B, B->C, C->A。现在有n个动物,编号为1-n,每个动物都是ABC的一种,但是不知道是哪一种,
现在有人用两种说法对这n个动物构成的食物链进行描述。
- 第一种说法:1 X Y 表示X和Y是同一类
- 第二种说法:2 X Y 表示X吃Y
这个人说了K句话,问有多少句话是假话。假话满足一下条件之一,否则就是真话
- 1) 当前的话与前面的某些真的话冲突,就是假话;
- 2) 当前的话中X或Y比N大,就是假话;
- 3) 当前的话表示X吃X,就是假话。
Sol:带权并查集。d[i]表示与根节点的关系,0表示同类,1表示i吃根节点,2表示根节点吃i
Code:
#include <iostream>
#include <vector>
#include <cstdio>
using namespace std;
// 食物链
// 带权并查集
struct UNF{
vector<int>p;
vector<int>d;
void init(int n)
{
for(int i = 0; i <= n + 10; ++i) {
p.push_back(i);
d.push_back(0);
}
}
int find(int x)
{
if(x != p[x]) {
int t = p[x];
p[x] = find(p[x]);
d[x] = (d[x] + d[t]) % 3;
}
return p[x];
}
void Union(int a, int b)
{
int pa = find(a);
int pb = find(b);
if(pa != pb)
{
p[pa] = pb;
}
}
};
int main(){
int n, k;
scanf("%d%d", &n, &k);
UNF ch;
ch.init(n);
int d, x, y;
int cnt = 0;
while( k -- )
{
scanf("%d%d%d", &d, &x, &y);
if(x > n || y > n || (x == y && d == 2)) ++ cnt;
else {
int px = ch.find(x);
int py = ch.find(y);
if(px != py) {
ch.p[px] = py;
ch.d[px] = (ch.d[y] - ch.d[x] + d - 1) % 3;
}
else {
-- d;
if(d != (ch.d[x] - ch.d[y] + 3) % 3) cnt ++;
}
}
}
cout << cnt << endl;
return 0;
}
题意:有n个人在玩石头剪刀布,然后有m个回合。其中一个是裁判,其余人分成a,b,c三组,同组的人只会出一个手势。
问你是否能找到这个裁判,如果能找到输出最早能在在第几回合能找到
如果有多个,输出"Can not determine"
如果不存在,输出"Impossible"
Sol:首先分组可以由并查集维护,同上题食物链一样,d[i]表示与根节点的关系,0表示同组,1表示输给根节点,2表示赢根节点
然后去枚举n个人作为裁判时的情况。如果有cnt个人符合
- cnt > 1 无法确定
- cnt == 0 不存在
- cnt == 1 存在唯一裁判,那么就是求最早在第几回合能判断出。我们只需找出其他人矛盾的最大回合。
#include <iostream>
#include <vector>
#include <cstdio>
using namespace std;
struct UNF{
vector<int>p;
vector<int>d;
void init(int n)
{
for(int i = 0; i <= n + 10; ++i) {
p.push_back(i);
d.push_back(0);
}
}
int find(int x)
{
if(x != p[x]) {
int t = p[x];
p[x] = find(p[x]);
d[x] = (d[x] + d[t]) % 3;
}
return p[x];
}
};
struct node{
int a, b;
int c;
}A[2020];
int idx;
int n, m;
bool judge(int u)
{
UNF ch;
ch.init(n);
for(int i = 1; i <= m; ++i)
{
int x = A[i].a;
int y = A[i].b;
int d = A[i].c;
if(x == u || y == u) continue;
int fx = ch.find(x);
int fy = ch.find(y);
if(fx == fy)
{
if(d != (ch.d[x] - ch.d[y] + 3) % 3) {
idx = max(idx, i);
return false;
}
}
else {
int px = ch.find(x);
int py = ch.find(y);
ch.p[px] = py;
ch.d[px] = (ch.d[y] - ch.d[x] + d + 3) % 3;
}
}
return true;
}
int main(){
while(scanf("%d%d", &n, &m) != EOF)
{
for(int i = 1; i <= m; ++i)
{
int a, b;
char ch;
scanf("%d%c%d", &a, &ch, &b);
if(ch == '=') A[i] = {a, b, 0};
else if(ch == '<') A[i] = {a, b, 1};
else A[i] = {a, b, 2};
}
idx = 0;
int ans = 0;
int cnt = 0;
for(int i = 0; i < n; ++i)
{
if( judge(i) ) {
++ cnt;
ans = i;
}
}
if(cnt==1) printf("Player %d can be determined to be the judge after %d lines\n", ans, idx);
else if(cnt > 1) printf("Can not determine\n");
else printf("Impossible\n");
}
return 0;
}
update 2021/12/19
题意:
有
q
q
q次次操作
sol
很容易想到用并查集来维护这些值变成了什么,但是当我们正着去做的时候,后面如果要改变前面已经该过了的值话就会出问题。比如前面你要把1变成2,后面又让1变成3,那么这样操作下来的话,最后1都会变成3 。那要怎么避免呢,我们将操作保存下来;然后反着去维护即可。具体细节看代码
Code
#include <iostream>
using namespace std;
const int N = 5e5 + 10;
int p[N];
int opt[N], x[N], y[N];
void solve()
{
int q;
cin >> q;
rep(i, 1, 5e5) p[i] = i;
for (int i = 1; i <= q; ++ i) {
cin >> opt[i] >> x[i];
if(opt[i] == 2) {
cin >> y[i];
}
}
vector<int> res;
for(int i = q; i ; i -- ) {
if(opt[i] == 1) res.push_back(p[x[i]]);
else {
p[x[i]] = p[y[i]];
}
}
reverse(ALL(res));
for(int x : res) cout << x << " ";
}
signed main(){
int T = 1;
// cin >> T;
while(T --) solve();
return 0;
}
因篇幅问题不能全部显示,请点此查看更多更全内容