题目链接:https://vjudge.net/problem/UVA-658
题意:补丁在修正bug时,有时也会引入新的bug。假定有n(n≤20)个潜在bug和m(m≤100)
个补丁,每个补丁用两个长度为n的字符串表示,其中字符串的每个位置表示一个bug。第一
个串表示打补丁之前的状态(“-”表示该bug必须不存在,“+”表示必须存在,0表示无所
谓),第二个串表示打补丁之后的状态(“-”表示不存在,“+”表示存在,0表示不变)。每
个补丁都有一个执行时间,你的任务是用最少的时间把一个所有bug都存在的软件通过打补
丁的方式变得没有bug。一个补丁可以打多次。
解题思路:
在任意时刻,每个bug可能存在也可能不存在,所以可以用一个n位二进制串表示当前软
件的“状态”。打完补丁之后,bug状态会发生改变,对应“状态转移”。是不是很像动态规
划?可惜动态规划是行不通的,因为状态经过多次转移之后可能会回到以前的状态,即状态
图并不是DAG。如果直接用记忆化搜索,会出现无限递归。
正确的方法是把状态看成结点,状态转移看成边,转化成图论中的最短路径问题,然后
使用Dijkstra或Bellman-Ford算法求解。不过这道题和普通的最短路径问题不一样:结点很
多,多达2 n 个,而且很多状态根本遇不到(即不管怎么打补丁,也不可能打成那个状态),
所以没有必要像前面那样先把图储存好。
还记得第7章中介绍的“隐式图搜索”吗?这里也可以用相同的方法:当需要得到某个结
点u出发的所有边时,不是去读G[u],而是直接枚举所有m个补丁,看看是否能打得上。不管
是Dijsktra算法还是Bellman-Ford算法,这个方法都适用。本题很经典,强烈建议读者编程实
现。
两个血坑点:
1:题目给的样例的第一个答案是错误的。应该是”Bugs cannot be fixed.”。。。。
2:inf必须要开到1<<20以上,这里我wa了无数发
具体细节见代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 110;
const int inf = 0x3f3f3f3f;
int d[(1<<20)+10], vis[(1<<20)+10];
int s[2][maxn], t[2][maxn];
int n, m, w[maxn];
string s1, s2;
void spfa()
{
int mask = (1<<n) - 1;
queue <int> que;
for(int i = 0; i < mask; i++){
vis[i] = 0;
d[i] = inf;
}
vis[mask] = 1;
d[mask] = 0;
que.push(mask);
while(!que.empty()){
int u = que.front(); que.pop();
vis[u] = 0;
for(int i = 0; i < m; i++){ //同时当需要得到某个结点u出发的所有边时,直接枚举所有m个补丁
if((u | s[1][i]) == u && ((u & s[0][i]) == u)){ //|代表对于s[1][i]有一个bug满足就可以,&代表对于s[0][i]必须全部满足
int v = u;
v = v | t[1][i];
v = v & t[0][i];
if(d[u] + w[i] < d[v]){
d[v] = d[u] + w[i];
if(!vis[v]){
que.push(v);
vis[v] = 1;
}
}
}
}
}
if(d[0] == inf){
printf("Bugs cannot be fixed.\n");
}
else{
printf("Fastest sequence takes %d seconds.\n", d[0]);
}
}
int main(){
int ks= 0;
//cout << 0x3f3f3f3f << endl;(血坑)
while(scanf("%d%d", &n, &m) != EOF && n)
{
memset(s, 0, sizeof(s));
memset(t, 0, sizeof(t));
for(int i = 0; i < m; i++){
cin >> w[i] >> s1 >> s2;
for(int j = 0; j < n; j++){
if(s1[j] == '+') s[1][i] += (1<<j); //必须存在
if(s1[j] != '-') s[0][i] += (1<<j); //排除必须不存在
if(s2[j] == '+') t[1][i] += (1<<j); //必须存在
if(s2[j] != '-') t[0][i] += (1<<j); //排除必须不存在
}
}
printf("Product %d\n", ++ks);
spfa();
printf("\n");
}
return 0;
}