kuangbin专题并查集

总结:
    1. 维护并查集的时候前面获得了pa=find(pa), 但是后面修改了p[pa],如果还使用之前的pa会造成错误,所以尽量在有改变之后都是用新的pa=find(a)
    2. 并查集可以用来维护不同的种类(维护和根节点的距离),一般用d[pa] = d[b] - d[a] + d来维护,d具体而定
    3. 区间加减变成s[r]和s[l-1]之间距离的维护
    4. 并查集判断是否是树,判断否成环,判断边=点-1,有向图的话还需要判断出度和入读
    5. 可以适当考虑下离线,先把所有查询读入,然后随着查询插入边或者倒着顺序查边,后者从前往后看就是删掉边了

真正的骗子

题目链接:https://www.acwing.com/activity/content/problem/content/6529/
    1. 并查集维护相对关系
    2. 01背包判断是否能唯一确定类别,当只有一种方案能组成是天使的时候,说明我们就可以确定是不是天使了
    3. 在并查集树根元素位置进行dp
    4. 背包具体方案的输出,需要使用二维数组,这样才能保存之前的信息
#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 1010;
int p[N], dist[N];
int f[N][N], st[N];
int w0[N], w1[N];
vector<int> res;
int n, p1, p2;

int find(int x){
    if(x != p[x]){
        int q = find(p[x]);
        dist[x] += dist[p[x]];
        p[x] = q; 
    }
    return p[x];
} 

int main(){

    while(scanf("%d %d %d", &n, &p1, &p2), n || p1 || p2){
        int m = p1 + p2;
        for(int i = 1; i <= m; i ++) p[i] = i, dist[i] = 0;
        for(int i = 1; i <= m; i ++ ) w0[i] = w1[i] = 0;
        for(int i = 0; i <= m; i ++){
            for(int j = 0; j <= m; j++){
                f[i][j] = 0;
            }
        }

        while(n -- ){
            int a, b;
            char jud[5]; 
            scanf("%d %d %s", &a, &b, jud);
            if(jud[0] == 'y'){
                int pa = find(a), pb = find(b);
                if(pa == pb) continue;
                p[pa] = pb;
                dist[pa] = (dist[b] - dist[a]) % 2;
            } 
            else{
                int pa = find(a), pb = find(b);
                if(pa == pb) continue;
                p[pa] = pb;
                dist[pa] = (dist[b] - dist[a] + 1) % 2;
            }
        }

        set<int> father;
        for(int i = 1; i <= m; i++){
            int pi = find(i);
            if((dist[i]%2+2)%2) w1[pi] ++;
            else w0[pi]++;
        }

        f[0][0] = 1;
        for(int i = 1; i <= m; i++){
            for(int j = 0; j <= m; j++){
                if(i == find(i)){
                    if(j >= w0[i]) f[i][j] = f[i][j] + f[i-1][j-w0[i]];
                    if(j >= w1[i]) f[i][j] = f[i][j] + f[i-1][j-w1[i]]; 
                }
                else{
                    f[i][j] = f[i-1][j];
                }

            }
        }

        if(f[m][p1] == 1){
            vector<int> res;
            int value = p1;
            for(int i = m; i >= 1; i--){
                int flag = 0;
                if(w0[i] != 0 && value >= w0[i]){
                    if(f[i-1][value - w0[i]] == 1){
                        value -= w0[i];
                        for(int j = 1; j <= m;  j++ ){
                            if(find(j) == i && (dist[j]%2+2)%2 == 0) res.push_back(j);
                        }
                        flag = 1;
                    }
                }
                if(!flag && w1[i] != 0 && value >= w1[i]){
                    if(f[i-1][value - w1[i]] == 1){
                        value -= w1[i];
                        for(int j = 1; j <= m; j++){
                            if(find(j) == i && (dist[j]%2+2)%2 == 1) res.push_back(j);
                        }
                    }
                }
            }
            sort(res.begin(), res.end());
            for(auto c : res) printf("%d\n", c);
            puts("end");

        }
        else puts("no");
    }    
    return 0;
}

超市

题目链接:https://www.acwing.com/problem/content/147/
贪心:按照价值从大到小对商品进行排序,依次遍历,如果能卖出当前遍历到的物品,那么就卖出他,并且在尽可能靠后的一天卖出,
(假如你没有在尽可能靠后的一天卖出去的,可能会使得后面本来能卖出去的商品,卖不出去了)
什么才是尽可能靠后的一天呢,显然是deadline那天,但是那天可能被之前的价值更大的物品占据了
所以这里需要使用并查集来寻找尽可能靠后的一天,p[i]表示i这一天前面那一天没有被占据,
发现他竟然就是并查集,具体从代码不难看出
#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 1e4+10;
int p[N];
struct Item{
	int value, deadline;
	bool operator < (const Item w) const{
		return value > w.value;
	}
}items[N];

int find(int x){
	if(x != p[x]) p[x] = find(p[x]);
	return p[x];
} 


int main(){
	int n;
	while(scanf("%d", &n) == 1){

		for(int i = 1; i <= n; i ++){
			int a, b;
			scanf("%d %d", &a, &b);
			items[i] = {a, b};
		}
		sort(items + 1, items + 1 + n);
		for(int i = 1; i <= 1e4; i ++) p[i] = i;
		int res = 0;
		for(int i = 1; i <= n; i ++){
			int ddl = items[i].deadline, value = items[i].value;
			int pi = find(ddl);
			if(pi > 0){
				p[pi] = pi - 1;
				res += value;
			}
		}
		printf("%d\n", res);
	}

    return 0;
}

石头剪子布

问题
    1. 注意数据范围,发现可以暴力枚举哪个是裁判
    2. 如果有多个符合条件,是不能确定,没有符合条件的是不可能,有唯一确定,并且排除其他人的可能性的最后的最后语句是确定的时间 
#include<bits/stdc++.h>

using namespace std;
typedef long long LL;
const int N = 510, M = 2010;
int p[N], d[N];

struct node{
	int a, b, type;
}nodes[M];

int find(int x){
	if(x != p[x]){
		int q = find(p[x]);
		d[x] += d[p[x]];
		p[x] = q;
	}
	return p[x];
}

bool join(int a, int b, int type){
	int pa = find(a), pb = find(b);
	if(pa == pb){
		if((d[b] - d[a] + type) % 3) return false;
	}
	else{
		p[pa] = pb;
		d[pa] = d[b] - d[a] + type;
	}
	return true;
}


int main(){
	int n, m;
	while(scanf("%d %d", &n, &m) == 2){
		for(int i = 0; i < m; i ++){
			int a, b; char op;
			scanf("%d%c%d", &a, &op, &b);
			if(op == '<') nodes[i] = {a, b, -1};
			else if(op == '>') nodes[i] = {a, b, 1};
			else nodes[i] = {a, b, 0};
		}
		int cnt = 0, who = -1, t = 0;
		for(int i = 0; i < n; i ++){
			int flag = 1;
			for(int j = 0; j < n; j ++) p[j] = j, d[j] = 0;
			for(int j = 0; j < m; j ++){
				if(nodes[j].a == i || nodes[j].b == i) continue;
				if(!join(nodes[j].a, nodes[j].b, nodes[j].type)) flag = 0, t = max(t, j);
				if(!flag) break;
			}
			if(flag) cnt ++, who = i;
		}
		if(cnt > 1) puts("Can not determine");
		else if(cnt == 0) puts("Impossible");
		else printf("Player %d can be determined to be the judge after %d lines\n", who, t + (t != 0));
	}
	return 0;
}
posted @ 2022-03-14 17:16  牛佳文  阅读(81)  评论(0)    收藏  举报