【题解】 P1041 传染病控制

如果你初学搜索,如何一步一步无伤A掉搜索真题?

让我们以初学者的角度走进改题;

警告:本篇题解**面向初学者**非最优解或非常规解法,神犇请绕道

做一道题首先需要的是逐步分析:

1. 题目给定了一棵树, 树的节点 **n <= 300** ,同理的变数p = n - 1;
或许是搜索?

2. 从节点1开始传染,所谓切断一条路径, 不难转换为标记其中一颗子树

3. 每个传染阶段为每一个已被标记“得病”的节点向下传染,传染次数在最坏情况下刚好为叶节点到1节点的距离; 或许是拓扑?

要寻找正确的解题方法总要进行不断的思考

首先先思考拓扑相关, 从末节点倒推是否是一种可行的方法?

事实证明这种方法在题目限制下是几乎不可行的;

(事实证明可以用来进行一定量的预处理)

那么对我来说就只有搜索一条路可以走;

然后是要求输出当传染人数最少的情况下的人数;

同理为未被传染人数最多时传染人数;

那么首先我们要建立相关的**代码框架**,

1.先从输入开始:

值得一提的是输入的边并没有说明是父节点指向子节点或子节点指向父节点

应该想方法处理

2.dfs函数

不难得到,一个节点若要被传染,那么**传染到该节点的第x个传染阶段一定是该节点到1节点的距离**;

接下来是我个人的想法:

显然以节点下标作为dfs传入参数来进行相关处理不太可行;

那么不如我们以**距离1节点的距离x为传入参数**

然后对所有距离1节点距离为x的节点进行处理

即为选择一颗子树进行切除, 然后进行下一层dfs

切除该子树要进行的操作为**标记所有该子树上的节点并统计节点数量**;

同时要保证该子树的父节点为在前几层的递归中未被切除;

当无子树可以切除时dfs函数变走到尽头

然后进行思考一下回溯, 这个是较简单的,同理与标记

```
int clean(int i){
    bol[i] = true;
    int num = 1;
    int p = f[i].size();
    for (int j = 0; j < p; ++j){
        num += clean(f[i][j]);
    }
    return num;
}
void reclean(int i){
    bol[i] = false;
    int p = f[i].size();
    for (int j = 0; j < p; ++j){
        reclean(f[i][j]);
    }
}
```

每次进行一次切除就要便利一遍全部子节点显然缺乏效率;

但对于本题的数据范围来说还是可以接受的;

上文代码中用到了一个vector, 里面存的是该节点的子节点

那么回到题目头, 我们该如何处理保证f数组里面都为该节点的子节点呢?

用另外一个数组存入输入的所有边

简单套用一个最短路模板统计距离

然后将所有合法的边push入f即可;

为了要保证可以正常处理距离节点1距离为x的全部节点

也要进行简单的统计

```
void resolve(int i, int cen){
    b[cen][cnt[cen]] = i;
    ++cnt[cen];
    int p = k[i].size();
    for (int j = 0; j < p; ++j){
        if (dis[k[i][j]] == dis[i]+1){
            resolve(k[i][j], cen+1);
            f[i].push_back(k[i][j]);
        }
    }
}
```

最后统计得未被传染人数最多时的人数

输出节点总数减未被传染人数获得正解;

具体细节详见代码

本人AC代码:(327ms)

```
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
#define LL long long
using namespace std;
int n, p, t1, t2, b[305][305], cnt[305], maxx, dis[305];
bool bol[305], vis[305];
vector <int> k[305], f[305];
struct node{
    int x, quan;
    node (int a, int b) : x(a), quan(b){
    }
    friend bool operator < (node a, node b){
        return a.quan > b.quan;
    }
};
int clean(int i){
    bol[i] = true;
    int num = 1;
    int p = f[i].size();
    for (int j = 0; j < p; ++j){
        num += clean(f[i][j]);
    }
    return num;
} //标记部分
void reclean(int i){
    bol[i] = false;
    int p = f[i].size();
    for (int j = 0; j < p; ++j){
        reclean(f[i][j]);
    }
} //回溯部分
void dfs(int cen, int tot){
    maxx = max(maxx, tot);
    for (int i = 0; i < cnt[cen]; ++i){
        if (!bol[b[cen][i]]){
            int num = clean(b[cen][i]);
            tot += num;
            dfs(cen+1, tot);
            reclean(b[cen][i]);
            tot -= num;
        }
    }
} //dfs核心函数
void resolve(int i, int cen){
    b[cen][cnt[cen]] = i;
    ++cnt[cen];
    int p = k[i].size();
    for (int j = 0; j < p; ++j){
        if (dis[k[i][j]] == dis[i]+1){
            resolve(k[i][j], cen+1);
            f[i].push_back(k[i][j]);
        }
    }
} //预处理第二部分
void solve(){
    priority_queue <node> que;
    for (int i = 0; i <= n; ++i) dis[i] = 999;
    dis[1] = 0;
    que.push(node(1, 0));
    while (!que.empty()){
        node temp = que.top();
        que.pop();
        int x = temp.x;
        int p = k[x].size();
        for (int j = 0; j < p; ++j){
            if (dis[k[x][j]] > dis[x]+1){
                dis[k[x][j]] = dis[x]+1;
                que.push(node(k[x][j], dis[k[x][j]]));
            }
        }
    }
    resolve(1, 0);
} //最短路算法进行预处理
//实际上以节点0开始进行拓扑排序效率更高
int main(){
    scanf("%d %d", &n, &p);
    for (int i = 0; i < p; ++i){
        scanf("%d %d", &t1, &t2);
        k[t1].push_back(t2);
        k[t2].push_back(t1);
    }
    solve();
    dfs(1, 0);
    printf("%d", n-maxx);
    //本人代码量命名较随意见谅pu~
}
```

那么,这道题就用最暴力却细腻的处理解决了;

代码亮点在哪里?要怎么写?

1. 分模块处理,即使只是简单的处理或回溯也不妨单独多出来一个函数方便进行相关的调试

2. 理解题意并尽快找到dfs函数所需要传入的参数(例如本题就要尽快从常规方法将节点序号作为传入参数中脱离出来寻找新的做法

3. 如果无法找到相关的关系,不妨进行一定量的预处理(例如本题中输入边未指明是父节点连向子节点或子节点连向父节点,而我只需要父节点连向子节点的相关边,就需要进行预处理即为单源最短路

4. 提高思维的深度, 拓宽思维的宽度

转载于:https://www.cnblogs.com/NHDR233/p/11246728.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值