天才钱和
学霸周
有一天,天才钱
和学霸周
闲的无聊玩起了游戏,游戏内容是这样的,现在有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算法,看是否次小生成树的权值是否等于最小生成树,不过复杂度有点大是o(m*n+mlogm)其中n表示枚举次数,m表示边数 (本题会tle)
就是删除最小生成树的一条边,再求次小生成树看权值是否与最小生成树相同;
方法二:先证明一个结论,次小生成树一定可以由最小生成树删除一个边再添上一个边得到。在证明中,我们可以得知当我们添加新边后需要删除新构成的环上的一条边,我们显然要删除 除了新边外 权值最大的那个边,这个我们可以o(n^2)先把最小生成树的所有路径中边权值最大的处理出来(dfs即可),此后我们只用枚举每一条新加入的边,然后o(1)的获得替换边,所以寻找的复杂度是 o(m+ n^2 ) 总体的复杂度为o(mlogm+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;
}