【解题记录】PAT1001 Battle Over Cities - Hard Version(35/35)

参考文章:

pat顶级1001 Battle Over Cities - Hard Version (35 point(s))_日沉云起的博客-CSDN博客

题述

网址:PTA | 程序设计类实验辅助教学平台

解题思路

        首先判断是一个图论的题目。因为最终的目的是保持整个图(除去被攻占的城市)连通,可以想到关于连通性的数据结构,也就是并查集。而主要操作要求花费最小的代价使得整张图连通,可以想到最小生成树。虽然和最小生成树不同,题目中本来已经有道路保持连通,相当于“花费最小的代价,使得子图连通”;但是根据Kruskal最小生成树算法的原理,算法基本没有改变。

        策略:按序号遍历每一个点,分别假设将每一个点以及与它相连的道路“抹去”,计算该节点被破坏后维持连通所造成的花费,将花费存于数组,并更新最大花费。最终将话费达到最大值的序号依次输出。

        对于单个节点被抹去之后的代价计算,首先要建立当前的联通情况。

        将并查集初始化,然后用可用的道路中不与被毁节点相连的道路建立并查集(即遍历所有输入中得到的可用道路,要加上判断该道路的某一端是不是被毁城市)。这样,就建立了当前的联通情况。

        然后,我们需要在status=0的道路中找到数条,使得修复这些路能使得整张图除了被毁城市都联通,并且代价最小。

        遍历开始时得到的status=0的道路(开始时存在一个数组中,并事先按照代价升序排序),遍历时要加条件跳过与被毁城市相连的可修复道路(因为城市被攻占了,不能够建立通向这座城市的道路)。采用Kruskal的贪心策略,如果当前可修复道路连接的是分属两个子图的城市(用并查集判断),则修复该道路。如果连接的是同一子图的两个城市,就跳过。遍历之后,就得到所需的最优解。

        贪心策略的正确性可以这样理解:假如一条道路连接的是子图内部的两个城市,那么选择它一定是浪费的,因为如果后续加入的一条道路使得这个子图与外部联通了,那么把这条内部连接的路去掉也不会有任何问题,所有的点依然都连通。而如果一条道路连接的是两个子图的不同城市,那么不选择它一定是浪费的,因为数组按照升序排列,即使后续通过其他的道路实现了相同的联通作用,代价也一定比选择这条道路更大。而且,子图之间的联通能且仅能通过连接这两个子图的两个节点得到。因此,贪心策略就是最优解。

        最后,在获取最优解之后,存储并更新最大值之前,还有一步操作:检查在这样的操作之后,整个图是否联通。因为Kruskal的最坏结果也就是把所有能修的道路都修了,如果整张图仍然不能联通,说明失去这座城市的代价是无穷大,用宏定义为INF。这样之后才可以更新最优解。

程序结构

        设计结构体保存道路的所有信息,并在输入的时候对道路进行判别,可用的存为一个数组,不可用的存为另一个数组;建立并查集需要前者,获取最优解需要后者。其他的包括并查集的所有函数。代码如下。

#include <iostream>
#include <algorithm>
#define MAXN 500
#define max(a,b) ((a)>(b)?(a):(b))
#define INF 1e7

//道路的存储结构
struct connection{
    int city1;
    int city2;
    int cost;
    bool status;
};

//并查集函数
int pre[MAXN+1];
void init(int n){
    for (int i=1;i<=n;i++){
        pre[i]=i;
    }
}

int find(int x){
    if (pre[x]==x) return x;
    else return pre[x]=find(pre[x]);
}

int check(int x,int y){
    return find(x) == find(y);
}

bool join(int x,int y){
    int xroot=find(x),yroot=find(y);
    if (xroot==yroot) return false;
    else pre[yroot]=xroot;
    return true;
}

//用于sort函数的compare
bool compare(connection a,connection b){
    return a.cost<b.cost;
}

int main(){

    //输入处理
    int n,m;
    scanf("%d%d",&n,&m);
    connection connections[m+1];
    int connections_p=0;
    connection toberepair[m+1];
    int repair_p=0;
    connection tmp;
    for (int i=1;i<=m;i++){
        std::cin>>tmp.city1>>tmp.city2>>tmp.cost>>tmp.status;
        if (tmp.status==1){
            connections[++connections_p]=tmp;
        }
        else{
            toberepair[++repair_p]=tmp;
        }
    }
    if (repair_p) std::sort(toberepair+1,toberepair+repair_p,compare);
    int cost[MAXN+1]={0};
    int maxcost=0;

    //遍历节点
    for (int i=1;i<=n;i++){
        init(n);

        //建立并查集
        for (int j=1;j<=connections_p;j++){
            if (connections[j].city1!=i and connections[j].city2!=i) join(connections[j].city1,connections[j].city2);
        }

        //选择最优解
        for (int j=1;j<=repair_p;j++){
            if (toberepair[j].city1!=i and toberepair[j].city2!=i){
                if (!check(toberepair[j].city1,toberepair[j].city2)){
                    join(toberepair[j].city1,toberepair[j].city2);
                    cost[i]+=toberepair[j].cost;
                }
            }
        }

        //判别是否整张图都联通
        int flag=0;
        for (int j=1;j<=n;j++){
            if (pre[j]==j && j!=i){
                if (flag==0) flag=1;
                else if (flag==1){
                    flag=2;break;
                }
            }
        }
        if (flag==2) cost[i]=INF;
        maxcost=max(maxcost,cost[i]);
    }

    //输出
    //如果本来就是联通的
    if (maxcost==0){
        printf("0");
        return 0;
    }

    //平凡情况输出
    int ans[50];
    int ans_p=0;
    for (int i=1;i<=n;i++){
        if (cost[i]==maxcost){
            ans[++ans_p]=i;
        }
    }
    for (int i=1;i<=ans_p;i++){
        printf("%d%c",ans[i],(i==ans_p)?'\0':' ');
    }
    return 0;
}

遇到的问题

        格式错误。对格式的判断是严格的,结尾不能有空格。小问题

        TLE。本来Kruskal的排序用的是自己写的快排,结果有一个点就tle了。想着也没什么东西好砍时间,不会是排序太慢的问题吧,结果换成内置的sort直接就过了。

        (好家伙)

        输入的特判。考虑边界情况,有可能一条道路都没有,也有可能只有一条道路。在这种情况下,sort会出问题:

if (repair_p) std::sort(toberepair+1,toberepair+repair_p,compare);

        如果没有if,就会发生repair_p=0报SF的情况。反正一条路和没有路不需要排序,过滤掉就得了。

        

        )个人感想:内置函数要是能用还是用内置的。不然都不知道自己写出来的东西有多菜(

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值