UVA658,隐式图+最短路+二进制子集枚举

来源:互联网 发布:jdbc连接数据库步骤 编辑:程序博客网 时间:2024/06/05 23:44

题目:

首先给出n和m,表示有n个bug和m个补丁。一开始存在n个bug,用1表示一个bug存在0表示不存在,所以一开始就是n个1,我们的目的是要消除所有的bug,所以目标状态就是n个0。对于每个补丁,会给出使用这个补丁的时间,另外会给出两个长度为n的字符串,第一个字符串表示这个补丁适用于什么情况下的bug,第二个字符串表示使用完这个补丁后原来的bug会变成怎么样。先说第一个字符串,s[i]=’0’,表示第i个bug存在与否都无所谓;s[i]=’+’,表示第i个bug一定要存在;s[i]=’-‘,表示第i个bug必须不存在;能不能使用这个补丁,就要看当前bug的状态是不是能不能全部满足第一个字符串,能的话就可以使用。第二个字符串表示使用完后的情况,ss[i]=’0’,表示第i个bug保持不变,原来是1就1是0就0;ss[i]=’+’,表示第i个bug必须为1;ss[i]=’-‘,表示第i个bug必须为0。

最终题目要求解的就是消除所有的bug并且用时最短,输出最短时间,如果bug不可能被完全消除那么就输出失败

思路:


可以看出是一个隐式图的最短路,但是因为可能在经过几个补丁后回到原本的状态,所以可能存在环,并不是DAG,所以动态规划无法解决,考虑使用Dijkstra或者SPFA:

第一次尝试:

需要考虑怎么来表示状态,因为最多只有二十个补丁,而且每个补丁存在与否的表示方法是‘-’或者‘+’,所以首先考虑string型字符串来储存每个状态。但是跑SPFA时候,数组d[maxn],inq[maxn]等无法直接用字符串来表示下标,所以考虑给状态进行标号。用vector<int>state 来保存当前所扩展出的所有状态,当扩展到一个新的状态v的时候,在state中查找,如果已经存在该状态直接用它的标号,否则将状态v加入state中并给它一个编号。代码如下

#include <cstdio>#include <iostream>#include <algorithm>#include <cstring>#include <string>#include <map>#include <queue>#include <vector>using namespace std;const int maxn=25;const int maxm=100+10;const int INF=2147483647;int inq[1<<maxn],cnt[1<<maxn],d[1<<maxn];int tim[maxm];int n,m,kase;vector<string>state;vector<string>before;vector<string>after;bool judge(int St, int Pat){    string st=state[St],pat=before[Pat];    for(int i=0;i<n;i++){        if(st[i]!=pat[i]&&pat[i]!='0')            return false;    }    return true;}int find(string st){    for(int i=0;i<state.size();i++){        if(st==state[i])            return i;    }    state.push_back(st);    return state.size()-1;}//work函数有问题int work(int ST,int PAT){    string st=state[ST],pat=after[PAT],newst;    for(int i=0;i<n;i++){        if(pat[i]=='0')            newst.push_back(st[i]);        else            newst.push_back(pat[i]);    }    int num=find(newst);    return num;}bool spfa(int s){    memset(inq,0,sizeof(inq));    memset(cnt,0,sizeof(cnt));    for(int i=0;i<(1<<n);i++)d[i]=INF;    queue<int>q;    d[s]=0;    q.push(s);    inq[s]=1;    while(!q.empty()){        int u=q.front();q.pop();        inq[u]=0;        for(int i=0;i<m;i++){            int newst;            if(judge(u,i)){                newst=work(u,i);//获得一个新的状态            if(d[u]<INF&&d[newst]>d[u]+tim[i]){                d[newst]=d[u]+tim[i];                if(!inq[newst]){                    q.push(newst);                    inq[newst]=1;                    if(++cnt[newst]>(1<<n))                        return false;                    }                }            }        }    }    return true;}int main(){    freopen("in.txt","r",stdin);    /*cin>>n;    string s1,s2,s3;    cin>>s1>>s2>>s3;    state.push_back(s1);    before.push_back(s2);    after.push_back(s3);    if(judge(0,0)){        cout<<"Yes"<<endl;        //cout<<state[0]<<" "<<before[0]<<" "<<after[0]<<endl;        int num=work(0,0);        cout<<state[num];    }else     cout<<"No"<<endl;     */    kase=0;     while(scanf("%d%d",&n,&m)&&n&&m){        kase++;        cout<<"Product "<<kase<<endl;        state.clear();        before.clear();        after.clear();        for(int i=0;i<m;i++){            cin>>tim[i];            string s1,s2;            cin>>s1>>s2;            before.push_back(s1);            after.push_back(s2);        }      string ss,gs;      for(int i=0;i<n;i++)        ss.push_back('+');      for(int i=0;i<n;i++)        gs.push_back('-');      state.push_back(ss);      state.push_back(gs);      int beg=0,en=1;      if(spfa(beg))        cout<<"Fastest sequence takes "<<d[en]<<" seconds."<<endl;      else        cout<<"Bugs cannot be fixed."<<endl;    }return 0;}

但是,上面的代码交上午会TLE,因为查找状态时最坏状态可能需要遍历所有的状态,会超时。

虽然上面代码无法AC,但是却在编写过程中遇到一个很有意思的问题,sting s1,s2;cin>>s1;for(int i=0;i<s1.length();i++)s2[i]=s1[i]后无法直接输出s2,而且在函数传递后也会出现问题,因为s2的内存没有被分配,只能使用s2.push_back(s1[i])或者s2+=s1[i]的方式来进行赋值。

LRJ在紫书给出的方法是,用一个n位的二进制串来表示当前状态,这样可以直接用这个二进制串来表示下标,且省略了查找状态这个步骤,可以直接跑dijkstra,LRJ大大的代码如下:

#include<cstdio>#include<cstring>#include<queue>using namespace std;struct Node {  int bugs, dist;  bool operator < (const Node& rhs) const {    return dist > rhs.dist;  }};const int maxn = 20;const int maxm = 100 + 5;const int INF = 1000000000;int n, m, t[maxm], dist[1<<maxn], mark[1<<maxn];char before[maxm][maxn + 5], after[maxm][maxn + 5];int solve() {  for(int i = 0; i < (1<<n); i++) { mark[i] = 0; dist[i] = INF; }  priority_queue<Node> q;  Node start;  start.dist = 0;  start.bugs = (1<<n) - 1;  q.push(start);  dist[start.bugs] = 0;  while(!q.empty()) {    Node u = q.top(); q.pop();    if(u.bugs == 0) return u.dist;    if(mark[u.bugs]) continue;    mark[u.bugs] = 1;    for(int i = 0; i < m; i++) {      bool patchable = true;      for(int j = 0; j < n; j++) {        if(before[i][j] == '-' && (u.bugs & (1<<j))) { patchable = false; break; }        if(before[i][j] == '+' && !(u.bugs & (1<<j))) { patchable = false; break; }      }      if(!patchable) continue;      Node u2;      u2.dist = u.dist + t[i];      u2.bugs = u.bugs;      for(int j = 0; j < n; j++) {        if(after[i][j] == '-') u2.bugs &= ~(1<<j);        if(after[i][j] == '+') u2.bugs |= (1<<j);      }      int& D = dist[u2.bugs];      if(D < 0 || u2.dist < D) {        D = u2.dist;        q.push(u2);      }    }  }  return -1;}int main() {    freopen("in.txt","r",stdin);  int kase = 0;  while(scanf("%d%d", &n, &m) == 2 && n) {    for(int i = 0; i < m; i++) scanf("%d%s%s", &t[i], before[i], after[i]);    int ans = solve();    printf("Product %d\n", ++kase);    if(ans < 0) printf("Bugs cannot be fixed.\n\n");    else printf("Fastest sequence takes %d seconds.\n\n", ans);  }  return 0;}

表示完状态后基本上就是裸的dijkstra,按照这个思路我又写了一个SPFA,如下


#include <cstdio>#include <algorithm>#include <cstring>#include <iostream>#include <queue>using namespace std;const int maxn=25;const int maxm=100+5;const int INF=2147483647;int tim[maxm];char before[maxm][maxn],after[maxm][maxn];int beg,gol,kase=0;int n,m;int d[1<<maxn],inq[1<<maxn],cnt[1<<maxn];int spfa(int s){    memset(inq,0,sizeof(inq));    memset(cnt,0,sizeof(cnt));    for(int i=0;i<(1<<n);i++)d[i]=INF;    queue<int>q;    q.push(s);d[s]=0;inq[s]=0;    while(!q.empty()){        int u=q.front();q.pop();        inq[u]=0;        for(int i=0;i<m;i++){                bool jud=1;            for(int j=0;j<n;j++){                if(before[i][j]=='+'&&!(u&(1<<j))){                    jud=0;break;                }                if(before[i][j]=='-'&&(u&(1<<j))){                    jud=0;break;                }            }                if(jud){                    int v=u;                    for(int j=0;j<n;j++){                        if(after[i][j]=='-')v=v&~(1<<j);                        if(after[i][j]=='+')v=v|(1<<j);                    }                    if(d[u]<INF&&d[v]>d[u]+tim[i]){                        d[v]=d[u]+tim[i];                        if(!inq[v]){                            q.push(v);                            inq[v]=1;                            if(++cnt[v]>=(1<<n))                                return false;                    }                }            }        }    }    return true;}int main(){   // freopen("in.txt","r",stdin);    while(scanf("%d%d",&n,&m)==2&&n&&m){            kase++;        for(int i=0;i<m;i++){            cin>>tim[i];            cin>>before[i]>>after[i];        }        beg=(1<<n)-1;        gol=0;        cout<<"Product "<<kase<<endl;        if(spfa(beg)&&d[gol]<INF){                cout<<"Fastest sequence takes "<<d[gol]<<" seconds."<<endl;                cout<<""<<endl;        }else{            cout<<"Bugs cannot be fixed."<<endl;            cout<<""<<endl;    }    }return 0;}

有一个很细微的区别,在跑dijkstra时候,如果扩展时u结点便是目标结点,那么可以直接返回u的值。因为dij中,当从优先队列取出u结点是,v(i)->u的所有v都已经被扩展过了(有个永久编号done[)],既d[u]已经是最优值了,但是spfa不是这样(应该··不是这样···一会再试一下····)。

另一个关于二进制串的问题,原本在自己写的时候,这一句:if(after[i][j]=='-')v=v&~(1<<j),是说对于第j个bug变为没有bug,本来写的是v=v^(1<<j),但是这样写可以的前提是第j个bug存在,但这里有可能本来第j个bug就不存在,所以不能直接用“异或”。而是要用先对于j取反在取交集。

另。二进制集合表示:

ALL=(1<<n)-1是1到n-1的全集。

(1<<j)表示的是单个的元素j