次小生成树的判定 CDOJ 1939

            天才钱和学霸周

有一天,天才钱学霸周闲的无聊玩起了游戏,游戏内容是这样的,现在有nn个城堡 
mm个不同的桥,每一个桥连接着两个不同的城堡,并且已知这mm个桥可以使nn个城堡连通,此外每一个桥都有重量vv。两位大爷需要给出选择桥的方案使得所有城堡被连通,注意两位大爷的方案不能完全相同(至少存在一个桥不相同),已知周大爷优先给出方案(因此钱大爷的方案必须不同于周大爷)。规则很诡异,如果钱大爷的方案中桥的重量之和≤≤周大爷的方案中桥的重量之和,那么钱大爷获胜,反之周大爷获胜。两位大爷都很聪明,他们会给出最优方案。现在你需要计算谁会赢。

Input

第一行输入两个值n(2≤n≤2000),m (n≤m≤200000) 
接下来m行,每一行输入三个值a (1≤a≤n),b(1≤b≤n),v(1≤v≤1018),其中a!=b

Output

如果钱大爷获胜输出“zin”,反之输出“ogisosetsuna” 。

Sample Input

2 2
1 2 1
1 2 1

Sample Output

zin

题意就是判断最小生成树是否唯一。

首先要想最小生成树不唯一就必须要存在权值相同的边。

判断最小生成树是否唯一(求次小生成树)的思路:

方法一:先跑一趟最小生成树,记录下来对应的边,由于次小生成树至少有一条边不同于最小生成树,所以我们可以枚举最小生成树的每一条边不在次小生成树中,然后跑一趟kursal算法,看是否次小生成树的权值是否等于最小生成树,不过复杂度有点大om*n+mlogm)其中n表示枚举次数,m表示边数                          (本题会tle)

就是删除最小生成树的一条边,再求次小生成树看权值是否与最小生成树相同;

方法二:先证明一个结论,次小生成树一定可以由最小生成树删除一个边再添上一个边得到。在证明中,我们可以得知当我们添加新边后需要删除新构成的环上的一条边,我们显然要删除 除了新边外 权值最大的那个边,这个我们可以on^2)先把最小生成树的所有路径中边权值最大的处理出来(dfs即可),此后我们只用枚举每一条新加入的边,然后o1)的获得替换边,所以寻找的复杂度是 om+ n^2 ) 总体的复杂度为omlogm+m+ n^2 ) ,当然我们不用担心数据范围,因为只用看,替换边的权值是否等于新加入边的权值。   (没懂T__T)

似乎是求出最小生成树之后,再加入一条边,这时肯定构成环了,所以就要删除除新加边之外权值最大的那条边。

好像明白了,那么就来做一个转换,枚举每一条边,只需要枚举权值相同的边就行啦,因为权值不同,一定无法使次小生成树和最小生成树权值相同;

那么现在考虑是否只要只要有权值相同的边,是否最小生成树就不唯一呢?很显然不是的,以为假设权值相同的边无限大,生成过程肯定不需要这样的边,自然可以想到要是用于生成最小生成树的边有权值一样的,那么最小生成树就不唯一。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=2e3+5;
int pre[10005];
int m;
int n;
int tot;
int find(int x)
{
    int root=x;
    while(root!=pre[root])
    {
        root=pre[root];
    }

    int i=x;
    int farther;
    while(pre[i]!=i)
    {
        farther=pre[i];
        pre[i]=root;
        i=farther;
    }
    return root;
}
bool join(int a,int b)
{
    int ra=find(a);
    int rb=find(b);
    if(ra!=rb)
    {
        pre[ra]=rb;
        return true;
    }
    return false;
}
struct path
{
    int from,to;
    long long  len;
}a[200005];
bool cmp (path t1,path t2)
{
    return t1.len<t2.len;
}
bool Kruskal()
{
    int k;
    int cnt1=0;
    int cnt2=0;
    sort(a,a+tot,cmp);
    for(int i=0;i<=n;++i)
        pre[i]=i;
    for(int i=0;i<tot;++i)
        {
            int j=i;
            while(j<m&&a[j].len==a[i].len)
            {
                int u=find(a[j].from);
                int v=find(a[j].to);
                if(u!=v) cnt1++;//找出生成最小生成树数权值相同的边
                j++;
            }
            j=i;
            while(j<m&&a[j].len==a[i].len)
            {
                if(join(a[j].from,a[j].to)) cnt2++;//找出生成最小生成树的边
                j++;
            }
            if(cnt2==n-1) break;
        }
    if(cnt1>cnt2)   printf("zin\n");
    else            printf("ogisosetsuna\n");

}
int main()
{

        scanf("%d%d",&n,&m);
        for(int i=0;i<m;++i)
            scanf("%d%d%lld",&a[i].from,&a[i].to,&a[i].len);
        tot=m;
        Kruskal();
        return 0;
}

猜你喜欢

转载自blog.csdn.net/codetypeman/article/details/81281756
今日推荐