HDOJ4786 Fibonacci Tree --- Kruskal算法+路径压缩并查集

题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=4786

Problem Description

  Coach Pang is interested in Fibonacci numbers while Uncle Yang wants him to do some research on Spanning Tree. So Coach Pang decides to solve the following problem:
  Consider a bidirectional graph G with N vertices and M edges. All edges are painted into either white or black. Can we find a Spanning Tree with some positive Fibonacci number of white edges?
(Fibonacci number is defined as 1, 2, 3, 5, 8, ... )

 

 

Input

  The first line of the input contains an integer T, the number of test cases.
  For each test case, the first line contains two integers N(1 <= N <= 105) and M(0 <= M <= 105).
  Then M lines follow, each contains three integers u, v (1 <= u,v <= N, u<> v) and c (0 <= c <= 1), indicating an edge between u and v with a color c (1 for white and 0 for black).

 

 

Output

  For each test case, output a line “Case #x: s”. x is the case number and s is either “Yes” or “No” (without quotes) representing the answer to the problem.

 

 

Sample Input

 

2 4 4 1 2 1 2 3 1 3 4 1 1 4 0 5 6 1 2 1 1 3 1 1 4 1 1 5 1 3 5 1 4 2 1

 

 

Sample Output

 

Case #1: Yes Case #2: No

题目大意:给t组测试数据。对于每组数据

输入n个点,m条边,然后m行对应每条边,每行输入每条边链接的点编号以及 边的颜色(1表示白色,0表示黑色)。

  问存不存在一个生成树中白边数量为Fibonacci数(即1,2,3,5,8...)

 

题解:可以看做每条边权值都一样,这样用Kruskal算法每次筛最小边时任意一条都算最小边。

可以先根据颜色排序,第一次优先选择黑色的边(贪心思想),第二次优先选择白色的边。

如果不能形成生成树,那直接no.

可以的话,可以算出形成生成树时,白色边数目的最小值min和最大值max,只要[min,max]之间存在Fibonacci数就可以满足题目条件(这个地方其实我暂时还没有完全想懂...)。大概比如说算出来最小6条,最大10条白边可以生成树,

那说明在10条白边那种情况下,删掉某4条白边,补上某4条黑边,可以转换为前一种情形,所以一定可以删某2条白边,补上2条黑边达到8条白边的生成树满足Fibonacci数的条件。

这道题要注意的是,用并查集找父亲节点也就是Find函数,要用路径压缩处理,不然会导致路径太长超时。(我刚开始就是没路径压缩,然后超时,不停比对别人的代码,改来改去才发现是这个原因造成的超时...)

 AC代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define MAXN 100005
using namespace std;
int pre[MAXN];// 并查集中的父亲节点
int n,m;// 点数和边数
bool fibonacci[MAXN];// 标记Fibonacci数
struct Edge {
  int u,v; // 连接的点编号
  int isWhite; // 是否是白色边 
}edge[MAXN];
// 找并查集中的祖先节点
int Find(int x) {
   if(x != pre[x]) pre[x] = Find(pre[x]);
    return pre[x];
}
bool cmp1(Edge a,Edge b) {      
      return a.isWhite < b.isWhite;
}
bool cmp2(Edge a,Edge b) {      
     return b.isWhite < a.isWhite;
}

void Init() {	 
       for(int i = 1;i <= n;i++) 
           pre[i] = i;
}

/**
 * 克鲁斯科尔算法求最小生成树
 * @return int 返回最少(多)的白边数,-1表示无法生成树
 */
int Kruskal() {
    Init();
    int ret = 0;// 返回值
    int edgeNum = 0;// 选取的边数    
    for(int i = 1;i <= m;i++) {
        int u = edge[i].u;
        int v = edge[i].v;
        int fu = Find(u);// u的祖先节点
        int fv = Find(v);// v的祖先节点
        if(fu == fv) {
            // 连接uv会形成闭环
            continue;
        }
        pre[fu] = fv;
        edgeNum++;
        if(edge[i].isWhite == 1)
            ret++;
        if(edgeNum == n - 1) {
            // 已经找到最小生成树
            break;
        }
    }
    if(edgeNum != n-1)
        return -1;
    return ret;    
}

void CalFibonacci() {
    memset(fibonacci,0,sizeof(fibonacci));
    fibonacci[1] = fibonacci[2] = fibonacci[3] = true;
    int cur = 3,last = 2;
    while(cur+last < MAXN) {
        fibonacci[cur+last] = true;
        int temp = last + cur;
        last = cur;
        cur = temp;
    }
}
int main()
{
   int t;
   int Case = 1;
   scanf("%d",&t);
   CalFibonacci();
   while(t--) {       
       scanf("%d%d",&n,&m);
       for(int i = 1;i <= m;i++) {    
           scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].isWhite);           
       }
       sort(edge+1,edge+1+m,cmp1);// 对边进行排序,边的编号从1开始
       int minNum = Kruskal();// 最小白边数
       sort(edge+1,edge+1+m,cmp2);// 对边进行排序,边的编号从1开始
       int maxNum = Kruskal(); 
	   if(minNum > maxNum)  swap(minNum,maxNum);
       if(minNum == -1) {
           printf("Case #%d: No\n",Case++);
           continue;
       }
       bool result = false;//标记是否满足题目要求
       for(int i = minNum;i <= maxNum;i++) {
           if(fibonacci[i]) {
               result = true;
               break;
           }
       }
       if(result)
           printf("Case #%d: Yes\n",Case++);
       else
           printf("Case #%d: No\n",Case++);
   }   
   return 0; 
} 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值