软件补丁问题(网络流24题)
洛谷题目链接:软件补丁问题
题目背景
none!
题目描述
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。
输入输出样例
说明
none!
如果让我给这道题取个名字,我会叫它位运算的艺术(因为这道题用了一些很有意思的位运算)。
这道题虽然是网络流24题之一,但是我并没有想到怎么用网络流来做。这里分享一波最短路的做法。
首先简单说一下题意:题目给出一个软件(???),并且它有n个错误,而现在有m个补丁来修复这些问题,第i个补丁的使用条件是软件包含 B1[i]中的所有错误,而不包含 B2[i]中的任何错误,得到的效果是修复软件中的某些错误 F1[i],而同时加入另一些错误 F2[i],以及代价是t[i],现在要求最小的代价使所有的错误都被修复。
那么看到这个错误总量N<=20,就可以采用一些比较好确定哪些位置有问题,比如状压。那么n个错误也就可以用n位来表示,若该位为1,则该位有错误,若为0,则没有。那么我们在存下判断是否可以使用补丁的b1,b2时也可以用状压的思想。那么我们就可以把状态看成点,耗费某个补丁的时间看成边,这个问题就转换成了从(1<<n)-1(有n个错误的状态)到0(所有状态都被消除)的最短路。那么这个问题就简单了,我们只需要判断从一个状态能转换到什么状态,并且记录下最短路径。
然后剩下的就是一些神奇的位运算操作了。
1.位逻辑运算符:
& (位 “与”) and (00001010 & 00000010 -> 00000010)
^ (位 “异或”)xor (00110010 ^ 11110001 -> 11000011)
| (位 “或”) or (00010001 | 11000101 -> 11010101)
~ (位 “取反”)(~10100100 -> 01011011)
2.移位运算符:
<<(左移)(00001001 << 2 -> 00100100)
>>(右移)(11010010 >> 3 -> 00011010)//左移或右移溢出时会直接过滤掉
利用这些操作,我们可以实现这道题的一些操作,推荐还是先自己想一下怎么实现,比较锻炼自己的能力。
另外重点强调:位运算的优先级非常低,比判断等于还要低!!!
下面看一下代码注释。
#include<bits/stdc++.h> using namespace std; const int M=100+5; const int N=22; int n, m; char s1[30], s2[30]; int dist[1<<N]; bool vis[1<<N]; void out(int); struct fix{ int b1, b2, f1, f2, t; }a[M]; int spfa(){ memset(dist,127,sizeof(dist)); queue <int> q; int s = (1<<n)-1 , inf = dist[0], nx; dist[s] = 0; vis[s] = 1; q.push(s); while(!q.empty()){//求最短路 int x = q.front(); q.pop(); vis[x] = 0; for(int i=1;i<=m;i++){ if(((~x)&a[i].b1) != 0) continue; if((x&a[i].b2) != 0) continue;//这两个判断与b1,b2的关系 nx = (x | a[i].f2); nx = (nx & (~a[i].f1));//如果可以打该补丁,则得到新状态nx,进行松弛 if(dist[nx] > dist[x] + a[i].t){ dist[nx] = dist[x]+a[i].t; if(!vis[nx]) vis[nx] = 1 , q.push(nx); } } } return dist[0]==inf?0:dist[0]; } int main(){ //freopen("data.in","r",stdin); int x; cin >> n >> m; for(int i=1;i<=m;i++){ scanf("%d%s%s",&x,s1,s2); a[i].t = x; for(int j=0;j<n;j++){ if(s1[j] == '+') a[i].b1 += 1<<n-j-1;//读入并压成二进制 if(s1[j] == '-') a[i].b2 += 1<<n-j-1; if(s2[j] == '+') a[i].f2 += 1<<n-j-1; if(s2[j] == '-') a[i].f1 += 1<<n-j-1; } } printf("%d\n",spfa()); return 0; }