[Luogu P2761] [网络流24题] 软件补丁问题

12 篇文章 0 订阅
8 篇文章 0 订阅

洛谷传送门

题目描述

T 公司发现其研制的一个软件中有 n 个错误,随即为该软件发放了一批共 m 个补丁程序。每一个补丁程序都有其特定的适用环境,某个补丁只有在软件中包含某些错误而同时又不包含另一些错误时才可以使用。一个补丁在排除某些错误的同时,往往会加入另一些错误。

换句话说,对于每一个补丁 i,都有 2 个与之相应的错误集合 B1[i]和 B2[i],使得仅当软件包含 B1[i]中的所有错误,而不包含 B2[i]中的任何错误时,才可以使用补丁 i。补丁 i 将修复软件中的某些错误 F1[i],而同时加入另一些错误 F2[i]。另外,每个补丁都耗费一定的时间。

试设计一个算法,利用 T 公司提供的 m 个补丁程序将原软件修复成一个没有错误的软件,并使修复后的软件耗时最少。对于给定的 n 个错误和 m 个补丁程序,找到总耗时最少的软件修复方案。

输入输出格式
输入格式:

第 1 行有 2 个正整数 n 和 m,n 表示错误总数,m表示补丁总数,1<=n<=20, 1<=m<=100。

接下来 m 行给出了 m 个补丁的信息。每行包括一个正整数,表示运行补丁程序 i 所需时间,以及 2 个长度为 n 的字符串,中间用一个空格符隔开。

第 1 个字符串中,如果第 k 个字符 bk 为“+”,则表示第 k 个错误属于 B1[i],若为“-”,则表示第 k 个错误属于 B21[i],若为“0”,则第 k 个错误既不属于 B1[i]也不属于 B2[i],即软件中是否包含第 k 个错误并不影响补丁 i 的可用性。

第 2 个字符串中,如果第 k 个字符 bk为“-”,则表示第 k 个错误属于 F1[i],若为“+”,则表示第 k 个错误属于 F2[i],若为“0”,则第 k 个错误既不属于 F1[i]也不属于 F2[i],即软件中是否包含第 k 个错误不会因使用补丁i 而改变。

输出格式:

程序运行结束时,将总耗时数输出。如果问题无解,则输出 0。

输入输出样例
输入样例#1:

3 3
1 000 00-
1 00- 0-+
2 0– -++

输出样例#1:

8

解题分析

显然可以看出此题是网络流状态压缩的最短路问题,将起点看作(1 <<(bug个数)) - 1(即全为1), 将终点视为0, 建立状态之间的边跑最短路即可,主要难点就在于状态的转移方法。

首先, 可以看出转移条件和转移结果需要分开存储, 所以我们可以使用两个值来分别保存, 但因为存在“0”、“+”、“-”三种状态, 所以我们可以分成四个变量。

接下来我们可以显然 ~~脑补~~得到以下转化关系:

转移条件:
需要的bug:因为必须存在, 所以将原值 & 转移值得到值等于转移值
不要的bug:因为必须不存在, 所以将原值 & 转移值得到0;

转移结果:
添加的bug:因为会添加上去(不管有没有), 所以将原值 | 转移值得到下一个状态。
修复的bug:因为会删掉选定的(不管有没有), 所以先将所有bug添加上去, 再进行一次删除得到下一个状态, 即先 | 再 ^。

代码如下:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <iomanip>
#include <queue>
using namespace std;
#define R register
#define W while
#define gc getchar()
#define IN inline
#define MX 1100005
struct Packet
{
    int add, delet, no, yes, cost;
}addons[1001];
queue <int> q;
int dist[MX];
bool inq[MX];
char s1[205], s2[205];
int main()
{
    int err, num;
    scanf("%d%d", &err, &num);
    for (R int i = 1; i <= num; ++i)
    {
        scanf("%d", &addons[i].cost);
        scanf("%s%s", s1, s2);
        for (R int j = 0; j < err; ++j)
        {
            if (s1[j] == '+') addons[i].yes ^= (1 << (err - j - 1));
            if (s1[j] == '-') addons[i].no  ^= (1 << (err - j - 1));
            if (s2[j] == '+') addons[i].add ^= (1 << (err - j - 1));
            if (s2[j] == '-') addons[i].delet ^= (1 << (err - j - 1));
        }
    }
    memset(dist, 63, sizeof(dist));
    q.push((1 << err) - 1);
    dist[(1 << err) - 1] = 0;
    inq[(1 << err) - 1] = true;
    R int now, nex;
    W (!q.empty())
    {
        now = q.front();
        q.pop();
        for (R int i = 1; i <= num; ++i)
        {
            if(!(now & addons[i].no) && !((now & addons[i].yes) ^ addons[i].yes))
            {
                nex = now | addons[i].add;
                nex = (nex | addons[i].delet) ; 
                nex = nex ^ addons[i].delet;
                if (dist[nex] > dist[now] + addons[i].cost)
                {
                    if (!inq[nex])
                    {
                        inq[nex] = true;
                        q.push(nex);
                    }
                    dist[nex] = dist[now] + addons[i].cost;
                }
            }
        }
        inq[now] = false;
    }
    if (dist[0] > 9999999) dist[0] = 0;
    printf("%d", dist[0]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值