Codeforcces 891C Envy kruskal

6 篇文章 0 订阅

原题链接:http://codeforces.com/problemset/problem/891/C

题目大意:给出n个点,m条边的图,(0<n,m<10^5),询问多次,每次询问给出一个边集,问是否存在一颗最小生成树包括给出的边集里的所有边。询问的边集的总大小不超过10^5。

题解:把所有边按照边权从小到大排序后,假设每次询问的边集大小都是1,那么只要知道比这条边权值小的所有边构成的边集里,由kruskal选出的边集是否会和这条边形成环。如果不会形成环,那么就是说如果选这条边而后产生的最小生成树不会比原来的最优解更差,那么如果扩展到边集大于1情况,也就是只要使得选上这个边集里的所有边都满足这个条件且内部没有环,似乎就可以判断出结果了。

但是,这样存在着反例,比如(1,2,4),(2,3,4),(3,4,3),(4,1,1)这样的边集,显然第一条边和第二条边不可能同时存在最小生成树里。那么就需要考虑同一个询问里同价值的边是否能同时存在于最小生成树里。也就是假设从小到大枚举到边权为v的边的时候,对于权值小于v的边由kruskal选出的边集,把同一个询问里的价值为v的边全部加入,如果不会形成环则说明至少这一部分是符合要求的,而某个边集符合要求的充要条件就是边集的每个部分都是符合条件的。

最后细节部分用并查集查询是否有环,按照边权排序来依次生成部分的最小生成树。

还是看代码吧:

#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline void read(int &x){
    char ch;
    bool flag=false;
    for (ch=getchar();!isdigit(ch);ch=getchar())if (ch=='-') flag=true;
    for (x=0;isdigit(ch);x=x*10+ch-'0',ch=getchar());
    x=flag?-x:x;
}

inline void read(long long &x){
    char ch;
    bool flag=false;
    for (ch=getchar();!isdigit(ch);ch=getchar())if (ch=='-') flag=true;
    for (x=0;isdigit(ch);x=x*10+ch-'0',ch=getchar());
    x=flag?-x:x;
}

inline void write(int x){
    static const int maxlen=100;
    static char s[maxlen];
        if (x<0) {   putchar('-'); x=-x;}
    if(!x){ putchar('0'); return; }
    int len=0; for(;x;x/=10) s[len++]=x % 10+'0';
    for(int i=len-1;i>=0;--i) putchar(s[i]);
}

const int MAXN = 600010;
const int MAXM = 600010;
struct Edge{
int st,ed,v;
}edge[ MAXN ];
int n , m , q;

vector<int> v_edge[ MAXN ];
vector< pair <int ,int > > v_que[ MAXN ];

int fa[ MAXN ],fa_now[ MAXN ];
int T_cnt[ MAXN ] , T;
bool ans[ MAXN ];

int find(int x){
return fa[x]?fa[x]=find(fa[x]):x;
}

int find_now(int x){
//printf("%d\n",x);
if ( T_cnt[x]!=T ) fa_now[x]=fa[x],T_cnt[x]=T;
return fa_now[x]?fa_now[x]=find_now(fa_now[x]):x;
}

int main(){
    read(n);read(m);
    for (int i = 1;i<=m;i++)
        {
            int a,b,c;
            read(a);read(b);read(c);
            edge[i].st=a;   edge[i].ed=b;   edge[i].v=c;
            v_edge[c].push_back(i);
        }
    int q ;
    read(q);
    for (int i = 1;i<=q;i++)
        {
            int t;
            read(t);
            for (int j = 1; j<= t ;j++)
                {
                    int x;
                    read(x);
                    v_que[ edge[x].v ].push_back( make_pair(i,x ) );
                }
        }
    T=0;
    for (int i = 1;i<=500000;i++)
        {
            for (int j = 0 ; j<v_que[i].size() ;j++)
                {
                       int id_que=v_que[i][j].first;
                       int id_edge=v_que[i][j].second;
                       if ( j == 0 || id_que!=v_que[i][j-1].first )
                            T++;
                       int x=find_now( edge[ id_edge ].st ), y = find_now ( edge[ id_edge ].ed  );
                       if( x==y )
                            ans[ id_que ] = 1;
                        else
                            fa_now[ x ] = y ;
                }
            for (int j = 0; j < v_edge[i].size() ; j++)
                {
                    int id_edge=v_edge[i][j];
                    int x=find( edge[ id_edge ].st ), y= find( edge[ id_edge ].ed  );
                    if ( x!=y )
                        fa[x]=y;
                }
        }
    for (int i = 1;i<=q;i++)
        if ( ans[i] )
            puts("NO");
        else
            puts("YES");
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Kruskal算法是一种基于贪心思想的最小生成树算法,其主要思想是将边按照权值从小到大排序,然后依次将边加入生成树中,如果加入该边不会构成环,则将该边加入生成树中。下面是用C语言实现Kruskal算法的代码: ```c #include <stdio.h> #include <stdlib.h> #define MAX_VERTEX_NUM 100 // 最大顶点数 #define MAX_EDGE_NUM 100 // 最大边数 typedef struct { int u; // 边的起点 int v; // 边的终点 int w; // 边的权值 } Edge; int parent[MAX_VERTEX_NUM]; // 并查集数组 // 比较函数,用于快速排序 int cmp(const void *a, const void *b) { return ((Edge *)a)->w - ((Edge *)b)->w; } // 查找x所在的集合 int find(int x) { if (parent[x] != x) { parent[x] = find(parent[x]); // 路径压缩 } return parent[x]; } // 合并x和y所在的集合 void merge(int x, int y) { int root_x = find(x); int root_y = find(y); if (root_x != root_y) { parent[root_x] = root_y; } } // Kruskal算法求最小生成树 void kruskal(Edge edges[], int n, int m) { int i, j; int cnt = 0; // 已选的边数 int sum = 0; // 权值和 for (i = 1; i <= n; i++) { parent[i] = i; // 初始化并查集 } qsort(edges, m, sizeof(Edge), cmp); // 对边按照权值排序 for (i = 0; i < m; i++) { int u = edges[i].u; int v = edges[i].v; int w = edges[i].w; if (find(u) != find(v)) { // u和v不在同一个集合中,加入该边不会构成环 merge(u, v); // 合并u和v所在的集合 printf("%d -> %d (%d)\n", u, v, w); // 输出该边 cnt++; sum += w; if (cnt == n - 1) { // 边数达到n-1,生成树已经构建完成 break; } } } printf("sum = %d\n", sum); // 输出最小生成树的权值和 } int main() { int n, m; Edge edges[MAX_EDGE_NUM]; printf("请输入顶点数和边数:"); scanf("%d%d", &n, &m); printf("请输入每条边的起点、终点和权值:\n"); int i; for (i = 0; i < m; i++) { scanf("%d%d%d", &edges[i].u, &edges[i].v, &edges[i].w); } kruskal(edges, n, m); // 求解最小生成树 return 0; } ``` 该代码中,使用了并查集来判断加入该边是否会构成环,使用了快速排序来对边按照权值排序。函数kruskal的参数分别为边数组edges,顶点数n和边数m,最终输出最小生成树的边和权值和。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值