【Kickstart】2018 Round F - Specializing Villages

解法

是Google Kickstart公开课里的解法:

首先,需要分析出,当每个点都找它们直接相邻的边里的最小值时,距离最近。每个点都只取与它直接相连且距离最小的那条边时,我们可以得到一张子图:
[外链图片转存失败(img-AzLi7slT-1562315720963)(quiver-image-url/31F02DCC828C6FB099EE1387BFD57340.jpg =90x94)]

可以证明这个子图里没有环:假如存在A->B->C->A的环,那么可以知道|AB|>|BC|>|CA|>|AB|,这就矛盾了

所以,这个子图是个没有环的无向图,因此一定是个森林。

对于森林里的每棵树,通过BFS分层之后,相邻两层的点着色不同就行了,单数层有2种着色法

剩下一种特殊情况:边长度为0
长度为0的边着色一定是不同的,与这条边相邻的点其实可以随意着色:
[外链图片转存失败(img-Pf2OdYLo-1562315720968)(quiver-image-url/8768E25DA6CFB5AEC95AB2A70AA32567.jpg =374x139)]

所以最后的算法是:

  1. 通过选择最近边,把子图抽出来
  2. 计算出子图有N个连通分量
  3. 计算出子图有M个与长度为0的边相连的点(但是它们的边长度不是0
  4. 最后的答案就是2^(M+N)
#include <stdio.h>
#include <string>
#include <iostream>
#include <memory.h>
#include <stdlib.h>
#include <cmath>
#include <unordered_map>
#include <map>
#include <queue>
#include <vector>
#include <algorithm>
#include <functional>

#define MAXN 60
#define INF 100010


using namespace std;

typedef long long lld;

int v,e;
int f[MAXN];
pair<int,int> nearest[MAXN];

int find(int x) {
    int r = x;
    while(f[r]!=r) r = f[r];
    while(f[x]!=r) {
        int tmp = f[x];
        f[x] = r;
        x = tmp;
    }
    return r;
}

bool join(int x,int y) {
    int rx = find(x), ry = find(y);
    if(rx!=ry) {
        f[rx] = ry;
        return true;
    }
    return false;
}

lld solve() {
    for(int i=1;i<=v;++i) {
        f[i] = i;
        nearest[i].first = 0;
        nearest[i].second = INF;
    }
    pair<int,int> zero;
    zero.first = zero.second = -1;
    for(int i=0;i<e;++i) {
        int a,b,l;
        scanf("%d%d%d",&a,&b,&l);
        if(l<nearest[a].second) {
            nearest[a].first = b;
            nearest[a].second = l;
        }
        if(l<nearest[b].second) {
            nearest[b].first = a;
            nearest[b].second = l;
        }
        if(l==0) {
            zero.first = a;
            zero.second = b;
        }
    }
    int n=v;
    for(int i=1;i<=v;++i) {
        if(join(i,nearest[i].first)) n--;
    }
//    printf("%d\n",n);
    if(zero.first!=-1) {
        for(int i=1;i<=v;++i) {
            if(i!=zero.first && i!=zero.second && (nearest[i].first==zero.first || nearest[i].first==zero.second)) {
                n++;
//                printf("node = %d, to %d, length = %d\n",i,nearest[i].first,nearest[i].second);
            }
        }
    }
//    printf("%d %d\n",m,n);
    return 1L<<n;
}

int main() {
//    freopen("/Users/huangyuemei/CLionProjects/untitled/B-small-practice.in","r",stdin);
//    freopen("/Users/huangyuemei/CLionProjects/untitled/my.out","w",stdout);
    int t;
    scanf("%d",&t);
    for(auto round=1;round<=t;++round) {
        scanf("%d%d",&v,&e);
        printf("Case #%d: %lld\n",round,solve());
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值