nefu暑假集训3 并查集与最小生成树 个人模板+例题汇总

前言:

  并查集与最小生成树的训练。

正文:

链接:并查集与最小生成树 - Virtual Judge (vjudge.net)

题目:

A - 关押罪犯

#include <bits/stdc++.h>
using namespace std;
const int N=200005;
int fa[N],d[N];
typedef struct stu{
	int a;
	int b;
	int c;
}node; 
node q[N];
int find(int x){
	if(fa[x]==x)return x;
	return fa[x]=find(fa[x]);
}
void merge(int x,int y){
	x=find(x);y=find(y);
	fa[x]=y;
}
bool cmp(node x,node y){
	return x.c>y.c;
}
int main(){
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;i++)fa[i]=i;
	for(int i=1;i<=m;i++){
		cin>>q[i].a>>q[i].b>>q[i].c;
	}
	sort(q+1,q+m+1,cmp);
	for(int i=1;i<=m+1;i++){
		if(find(q[i].a)==find(q[i].b)){
			cout<<q[i].c;
			break;
		}
		else{
			if(!d[q[i].a])d[q[i].a]=q[i].b;
			else merge(d[q[i].a],q[i].b);
			if(!d[q[i].b])d[q[i].b]=q[i].a;
			else merge(d[q[i].b],q[i].a);
		}
	}
	return 0;
}

我们先将所有囚犯之间的矛盾按怨气大小排序,怨气最大的那两个人一定是优先放在俩个地方,我们枚举排序过的囚犯组合,先判断他们是否再同一并查集内,若是则直接输出答案,不是就分别判断他俩目前是否有敌人,若无则将敌人记录下来,若有则将该囚犯的两个敌人合并至一个并查集内(敌人的敌人就是朋友),不断进行以上操作最终可以得到答案。

B - Shichikuji and Power Grid

 

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
typedef pair<int,int> PII;
struct node{
	int x,y,c,k;
}a[2005];
struct nod{
	int fa,ne,w; 
}p[4000040];
int cnt = 0;
int sum = 0;
vector<int> ans;
vector<PII> res; 
int f[2005];
int find(int x){
	if(f[x] == x)
	return x;
	return f[x] = find(f[x]); 
}
bool cmp(nod a,nod b){
	return a.w < b.w;
} 
void solve(){
	int n;
	cin >> n; 
	for(int i = 1;i <= n;i++){
		cin >> a[i].x >> a[i].y;
	}
	for(int i = 1;i <= n;i++)
	f[i] = i;
	for(int i = 1;i <= n;i++){
		cin >> a[i].c;
		p[++cnt].fa = 0;
		p[cnt].ne = i;
		p[cnt].w = a[i].c;
	}
	for(int i = 1;i <= n;i++){
		cin >> a[i].k;
	}
	for(int i = 1;i <= n;i++){
		for(int j = i + 1;j <= n;j++){
			p[++cnt].fa = i;
			p[cnt].ne = j;
			p[cnt].w = (abs(a[i].x - a[j].x) + abs(a[i].y - a[j].y))*(a[i].k + a[j].k);
		}
	}
	sort(p + 1,p +1 + cnt,cmp);
	int s = 0;
	for(int i = 1;i <= cnt;i++){
		int x = find(p[i].fa);
		int y = find(p[i].ne);
		if(x == y){
			continue;
		}
		if(p[i].fa == 0){
			ans.push_back(p[i].ne);
		}
		else{
			res.push_back({p[i].fa,p[i].ne});
		}
		f[x] = y;
		sum += p[i].w;
		s++; 
		if(s == n)
		break;
	}
	cout << sum <<"\n";
	cout << ans.size() <<"\n";
	for(auto i:ans)	
	cout <<i << ' ';
	cout <<"\n";
	cout << res.size() <<"\n";
	for(auto t:res){
		cout <<t.first <<" "<<t.second <<"\n";
	}
}
signed main(){
	int t = 1;
	while(t--)
	{
		solve(); 
	}
	return 0;
}

超级源点问题。

C - Power Tree:(待补)

 

D - 食物链

 

#include <bits/stdc++.h>
using namespace std;
const int N = 50010;
int n, m;
int p[N], d[N];
int find(int x){
    if (p[x] != x){
        int t = find(p[x]);
        d[x] += d[p[x]];
        p[x] = t;
    }
    return p[x];
}
int main(){
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) p[i] = i;
	int res = 0;
    while (m -- ){
        int t, x, y;
        scanf("%d%d%d", &t, &x, &y);
        if (x > n || y > n) res ++ ;
        else{
            int px = find(x), py = find(y);
            if (t == 1){
                if (px == py && (d[x] - d[y]) % 3) res ++ ;
                else if (px != py){
                    p[px] = py;
                    d[px] = d[y] - d[x];
                }
            }
            else{
                if (px == py && (d[x] - d[y] - 1) % 3) res ++ ;
                else if (px != py){
                    p[px] = py;
                    d[px] = d[y] + 1 - d[x];
                }
            }
        }
    }
    printf("%d\n", res);
    return 0;
}

非常经典的带权并查集题目了,我们可以开一个数组d,d[i]表示i这个点到根节点的距离,我们我们将同类的点距离设为0,捕食关系距离设为1,被捕食关系距离设为2,我们就可以据此通过两个点之间的距离%3来判断他们之间的关系。

E - How Many Answers Are Wrong:(待补)

 

F - 银河英雄传说

 

#include <bits/stdc++.h>
using namespace std;
const int N=300005;
int fa[N],d[N],a[N];
int find(int x){
	if(fa[x]!=x){
		int u=fa[x];
		fa[x]=find(fa[x]);
		d[x]+=d[u];
		a[x]=a[fa[x]];
	}
	return fa[x];
}
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=300000;i++){
		fa[i]=i;d[i]=0;a[i]=1;
	}
	for(int i=1;i<=n;i++){
		char s;
		int x,y,fx,fy;
		cin>>s>>x>>y;
		if(s=='M'){
			fx=find(x);fy=find(y);
			fa[fx]=fy;
			d[fx]+=a[fy];
			a[fx]+=a[fy];a[fy]=a[fx];
		}
		if(s=='C'){
			fx=find(x);fy=find(y);
			if(fx!=fy){
				cout<<-1<<endl;
				continue;
			}
			else{
				cout<<abs(d[x]-d[y])-1<<endl;
			}
		}
	}
	return 0;
}

这题和上题类似,不过多了一个a数组,主要作用是维护连通块的大小以正确表达出该点到根节点的距离。

G - News Distribution

 

#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int maxn=1e6+5;
int n,m,f[maxn],num[maxn];
int find_x(int x) {
	if(f[x]!=x)return f[x]=find_x(f[x]);
	else return f[x];
}
void unite(int x,int y) {
	int aa=find_x(x);
	int bb=find_x(y);
	if(aa!=bb)f[aa]=bb;
}
int main() {
	IOS;
	cin>>n>>m;
	for(int i=0; i<=n; i++)f[i]=i;
	while(m--){
		int k,a,b;
		cin>>k;
		if(k!=0)cin>>a;
		for(int i=1; i<k; i++) {
			cin>>b;
			unite(a,b);
		}
	}
	for(int i=1; i<=n; i++)num[find_x(i)]++;
	cout<<num[find_x(1)];
	for(int i=2; i<=n; i++)cout<<" "<<num[find_x(i)];
	cout<<endl;
}
 

题目可理解为输出1 - n每个号码所在团体总人数,利用并查集不断向集合添加成员,记录每个集合的人数,保存在根节点的sum[]中,查询每个节点的根节点,输出根节点sum

H - Phase Shift(待补):

 

I - 星球大战:​​​​​​​

#include <bits/stdc++.h>
using namespace std;
const int N = 4 * 1e5 + 10; // 全局变量;
int res;    // 储存答案;
int n, m, k;
// n 星球数目, m 以太隧道数目, k 遭受攻击的星球数目
int fa[N];
// 并查集
int h[N], s[N], e[N], ne[N], idx;
// 存图, e[] 存边到达的终点, s 存边的起点
bool st[N];

void add(int a, int b) 
{
    e[idx] = b, s[idx] = a, ne[idx] = h[a], h[a] = idx ++;
}

int find(int x) // 并查集, 找祖先 (路径压缩)
{
    if (x == fa[x]) return x;
    return fa[x] = find(fa[x]);
}

int main()
{
    memset(h, -1, sizeof h);    // 初始化表头
    memset(st, true, sizeof st);// 初始时没有点被摧毁

    scanf("%d %d", &n, &m);

    for (int i = 1; i <= n; i ++ ) fa[i] = i;

    for (int i = 1; i <= m; i ++ )  
    {
        int x, y;

        scanf("%d %d", &x, &y);
        add(x, y), add(y, x);   // 读入无向边
    }

    scanf("%d", &k);    // 读入遭受攻击的星球数目

    stack<int> pk, ans; 
    // 分别储存 pk 被攻击的星球, ans 输出答案
    for (int i = 1; i <= k; i ++ )
    {
        int q;
        scanf("%d", &q);

        st[q] = false, pk.push(q);  
        // 标记为摧毁, 算法核心是将摧毁转换成 修建 , 所以逆时间顺序压入栈中
    }

    m = 2 * m, res = n - k;
    // 无向边边数 * 2, res 储存连通块数量
    for (int i = 1; i <= m; i ++ )  // 先找所有完好的边构成的连通块数量
    {
        int S = find(s[i]), E = find(e[i]); 
        // 先找到这条边的入点和出点
        if(st[e[i]] && st[s[i]] && S != E)  // 如果这两条边都没有被摧毁, 且不在同一个连通块中
        {
            res --;     // 连通块的数量就可以减一了
            fa[E] = S;  // 加入同一个连通块
        }
    }

    ans.push(res);  // 逆时间的初始答案, k 个点都被摧毁 或 理解为没有新增的点
    while(pk.size())    // 只要不为空
    {
        int tot = pk.top(); // 取出栈顶
        pk.pop();
        res ++;         // 多了一个星球,连通块数量加一
        st[tot] = true; // 当前星球已被修建

        for (int i = h[tot]; i != -1; i = ne[i])    // 遍历这个点的所有出边
        {
            int j = e[i];   // 边的终点
            int S = find(tot), E = find(e[i]);  // 找到起点和终点的祖先
            if (st[j] && S != E)    // 终点没有被摧毁, 不是一个祖先也就是不在一个连通块中
            {
                res --;     // 连通块数量减一
                fa[E] = S;  // 加入一个连通块
            }
        }
        ans.push(res); // 压入当前修建好这个星球的答案
    }
    while (ans.size()) // 逆输出答案
    {
        res = ans.top();
        ans.pop();
        printf("%d\n", res);
    }

    return 0;
}

 逆向过程建图。

J - 货车运输:​​​​​​​

 

#include<bits/stdc++.h>  
const int  MAXN =10005 ;
const long long  INF =999999999; 
using namespace std; 
struct Edge1{  
    int x,y,dis;
}edge1[50005]; 
struct Edge2{
    int to,next,w;
}edge2[100005];  
int cnt,n,m,head[MAXN],deep[MAXN],f[MAXN],fa[MAXN][21],w[MAXN][21];
bool vis[MAXN]; 
void addedge(int from, int to, int w){
    edge2[++cnt].next=head[from];
    edge2[cnt].to=to;
    edge2[cnt].w=w;
    head[from]=cnt;
    return ;
}

bool CMP(Edge1 x, Edge1 y){
    return x.dis>y.dis;
}

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

void kruskal(){
    sort(edge1+1, edge1+m+1, CMP); 
    for(int i=1; i<=n; i++)
        f[i]=i;  
    for(int i=1; i<=m; i++)
        if(find(edge1[i].x)!=find(edge1[i].y)){
            f[find(edge1[i].x)]=find(edge1[i].y);
            addedge(edge1[i].x, edge1[i].y, edge1[i].dis);
            addedge(edge1[i].y, edge1[i].x, edge1[i].dis); 
			}
    return ;
}

void dfs(int node){
    vis[node]=true;
    for(int i=head[node]; i; i=edge2[i].next){ 
        int to=edge2[i].to;
        if(vis[to]) continue;
        deep[to]=deep[node]+1; 
        fa[to][0]=node; 
        w[to][0]=edge2[i].w;  
        dfs(to);
    }
    return ;
}

int lca(int x, int y)
{
    if(find(x)!=find(y)) return -1; 
    int ans=INF;
    if(deep[x]>deep[y]) swap(x,y);  
    for(int i=20; i>=0; i--)
        if(deep[fa[y][i]]>=deep[x]){
            ans=min(ans, w[y][i]); 
            y=fa[y][i]; 
        }
    if(x==y) return ans;
  
    for(int i=20; i>=0; i--)
        if(fa[x][i]!=fa[y][i]){
            ans=min(ans, min(w[x][i], w[y][i]));
            x=fa[x][i]; 
            y=fa[y][i];
        }
    ans=min(ans, min(w[x][0], w[y][0]));
    return ans;
}

int main()
{
    int x,y,z,q;
    scanf("%d%d",&n,&m);
    for(int i=1; i<=m; i++){
        scanf("%d%d%d",&x,&y,&z);
        edge1[i].x=x;
        edge1[i].y=y;
        edge1[i].dis=z;
    }
    kruskal();
    for(int i=1; i<=n; i++)
        if(!vis[i]){
            deep[i]=1; 
            dfs(i);
            fa[i][0]=i;
            w[i][0]=INF;
        }
    for(int i=1; i<=20; i++)
        for(int j=1; j<=n; j++){
            fa[j][i]=fa[fa[j][i-1]][i-1]; 
            w[j][i]=min(w[j][i-1], w[fa[j][i-1]][i-1]);
        }
    scanf("%d",&q);
    for(int i=1; i<=q; i++){
        scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y)); 
    }
    return 0;
} 

最大生成树+lca倍增。

K - 程序自动分析:​​​​​​​

 

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_map>

using namespace std;

const int N = 200010;

int n, m;
int p[N];
unordered_map<int, int> S;

struct Query
{
    int x, y, e;
}query[N];

int get(int x)
{
    if (S.count(x) == 0) S[x] = ++ n;
    return S[x];
}

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

int main()
{
    int T;
    scanf("%d", &T);
    while (T -- )
    {
        n = 0;
        S.clear();
        scanf("%d", &m);
        for (int i = 0; i < m; i ++ )
        {
            int x, y, e;
            scanf("%d%d%d", &x, &y, &e);
            query[i] = {get(x), get(y), e};
        }

        for (int i = 1; i <= n; i ++ ) p[i] = i;

        // 合并所有相等约束条件
        for (int i = 0; i < m; i ++ )
            if (query[i].e == 1)
            {
                int pa = find(query[i].x), pb = find(query[i].y);
                p[pa] = pb;
            }

        // 检查所有不等条件
        bool has_conflict = false;
        for (int i = 0; i < m; i ++ )
            if (query[i].e == 0)
            {
                int pa = find(query[i].x), pb = find(query[i].y);
                if (pa == pb)
                {
                    has_conflict = true;
                    break;
                }
            }

        if (has_conflict) puts("NO");
        else puts("YES");
    }

    return 0;
}

见注释。

L - 蔬菜:(待补)

 这个真不会捏。

后记:

  里面有些题难度已经很高了,我连题解都看不懂,以后再补吧!

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值