紫书例题 11-6 Uva658 SPFA,血坑

题目链接: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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值