Libre 6009 「网络流 24 题」软件补丁 / Luogu 2761 软件安装问题 (最短路径,位运算)...

Libre 6009 「网络流 24 题」软件补丁 / Luogu 2761 软件安装问题 (最短路径,位运算)

Description

T 公司发现其研制的一个软件中有 n 个错误,随即为该软件发放了一批共 m 个补丁程序。每一个补丁程序都有其特定的适用环境,某个补丁只有在软件中包含某些错误而同时又不包含另一些错误时才可以使用。一个补丁在排除某些错误的同时,往往会加入另一些错误。换句话说,对于每一个补丁 i,都有 2 个与之相应的错误集合 B1[i]和 B2[i],使得仅当软件包含 B1[i]中的所有错误,而不包含 B2[i]中的任何错误时,才可以使用补丁 i。补丁 i 将修复软件中的某些错误 F1[i],而同时加入另一些错误 F2[i]。另外,每个补丁都耗费一定的时间。试设计一个算法,利用 T 公司提供的 m 个补丁程序将原软件修复成一个没有错误的软件,并使修复后的软件耗时最少。对于给定的 n 个错误和 m 个补丁程序,找到总耗时最少的软件修复方案。

Input

第 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 而改变。

Output

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

Sample Input

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

Sample Output

8

Http

Libre:https://loj.ac/problem/6009
Luogu:https://www.luogu.org/problem/show?pid=2761

Source

最短路径,位运算

解决思路

话说这不是一道最短路径的题目吗?为什么放在了网络流里面?不懂╮(╯▽╰)╭
我们用位运算的方式表示一个错误有没有修复。对于错误i,若第i-1位是1则表示该错误存在,否则表示不存在。而每一次转移就根据位运算的操作来判断是否满足某些错误存在而另一些错误不存在的情况。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

const int maxN=300;
const int maxM=maxN*maxN*2;
const int inf=2147483647;

int n,m;
unsigned int B1[maxN];//B1,B2,F1,F2的意义与题目一样
unsigned int B2[maxN];
unsigned int F1[maxN];
unsigned int F2[maxN];
int Cost[maxN];
int Q[(1<<21)];
int Dist[(1<<21)];
bool inqueue[(1<<21)];

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
    {
        char str[maxN];
        scanf("%d%s",&Cost[i],str);
        for (int j=0;j<n;j++)
            if (str[j]=='+')
                B1[i]=B1[i]|(1<<(j));//这里是标记某个错误是否出现,注意标号从0开始
            else
                if (str[j]=='-')
                    B2[i]=B2[i]|(1<<j);
        scanf("%s",str);
        for (int j=0;j<n;j++)
            if (str[j]=='+')
                F1[i]=F1[i]|(1<<j);
            else
                if (str[j]=='-')
                    F2[i]=F2[i]|(1<<j);
    }
    for (int i=0;i<=(1<<n)-1;i++)
        Dist[i]=inf;
    int h=1,t=0;
    Q[1]=(1<<n)-1;//开始时所有错误都存在
    Dist[(1<<n)-1]=0;
    inqueue[(1<<n)-1]=1;
    do
    {
        t++;
        int u=Q[t];
        inqueue[u]=0;
        for (int i=1;i<=m;i++)
        {
            unsigned int now=u;
            if (((now&B1[i])==B1[i]) && (((~now)&B2[i])==B2[i]))//要满足B1[i]中的错误都存在且B2[i]中的错误都不存在才能加入该补丁
            {
                now=now&(~F2[i]);//先去掉该补丁修复的错误
                now=now|F1[i];//再加上该补丁加上的错误
                if (Dist[now]>Dist[u]+Cost[i])//更新最优值
                {
                    Dist[now]=Dist[u]+Cost[i];
                    if (inqueue[now]==0)
                    {
                        h++;
                        Q[h]=now;
                        inqueue[now]=1;
                    }
                }
            }
        }
    }
    while (h!=t);
    if (Dist[0]==inf)//注意输出无解
        cout<<0<<endl;
    else
        cout<<Dist[0]<<endl;
    return 0;
}

转载于:https://www.cnblogs.com/SYCstudio/p/7291338.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值