最短路(隐式图搜索)
题目是过了,不过时间好慢,用了2.200s。大概是没有用位运算的原因,我是直接模拟的。而解决最短路的问题是用spfa来做(感觉这种思路更加形象和容易理解,并且更符合隐式图搜索的感觉)
来说说题意,这个题意都是很长很烦的。首先给出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不可能被完全消除那么就输出失败
这个题目可以转化为最短路的模型来求解。由n个1或0来表示bug,我们很容易联想要二进制和十进制的转化,对于当前的bug状态,我们可以转化为1个十进制来表示,那么一开始的状态显然就是2^n-1,目标状态就是0,也就是从2^n-1转化为0,用时最少,相当于从2^n-1到0的最短路
对于一个补丁,其实就是一些有向边(是有向边,而且不是一条,可能是多条,所以是一些),为什么?因为对于当前的bug状态我们转化为十进制u,扫描所有的补丁,找到可以使用的补丁,并在这个补丁作用下变为一个新的bug状态,这个新的bug状态也对应一个十进制v,所以其实就是u到v,有向边u-->v,边权就是使用补丁使用的时间。
一种思路是先建图,再来一个最短路,但是会超时,因为图的顶点太多,边也很多。n的上限是20,即顶点个数为2^20-1,而边数在最坏情况下很大的,一定会超时。而我们思考可以知道,很多顶点是不一定会经过的,也就是很多bug的状态不会出现,所以我们为什么不一边最短路一边建图呢?所以就用spfa来做。而这题边权是不会为负的,所以也可以用dij
这题只要处理好细节,AC是没问题,但是想时间快点,就要用一些技巧。主要区别在于,怎么判断哪些补丁适用于当前的bug,另外使用后怎么快速得到新的bug状态等等,这些都可以用位运算来提高速度。
另外就是如果用dij的优先队列应该会比spfa更快。
另外注意一个问题,不知道是我自己的代码的问题还是本质上的错误。就是关系bug二进制的保存。好像10000,表示16,一开始我为了方便运算是逆过来保存00001,然后去计算新的状态也是逆过来保存,然后就一直TLE。后来我改回来顺着保存,就AC了。
代码有点长…………
#include <cstdio> #include <cstring> #include <queue> using namespace std; #define INF 0x3f3f3f3f #define N 25 #define M 110 #define MAX 1048600 int n,m,s,t; int d[MAX],vis[MAX],a[N]; int bug[N],newbug[N]; struct patches { char b0[N],b[N]; int t; }; typedef struct patches patches; patches p[M]; void get_bug(int x) { //memset(bug,0,sizeof(bug)); for(int i=n-1; i>=0; i--) { bug[i]=x%2; x/=2; } /* printf("get_bug\n"); for(int i=0; i<n; i++) printf("%d",bug[i]); printf("\n"); */ return ; } int can_use(int k) { int OK=1; for(int i=0; i<n; i++) { if(p[k].b0[i]=='+' && bug[i]==0) { OK=0; break;} else if(p[k].b0[i]=='-' && bug[i]==1) { OK=0; break;} } return OK; } int get_newbug(int k) { //memset(newbug,0,sizeof(newbug)); for(int i=n-1; i>=0; i--) { if(p[k].b[i]=='0') newbug[i]=bug[i]; else if(p[k].b[i]=='+') newbug[i]=1; else newbug[i]=0; } int val=0; for(int i=n-1,j=0; i>=0; i--,j++) val+=newbug[i]*a[j]; /* printf("get_newbug\n"); for(int i=0; i<n; i++) printf("%d",newbug[i]); printf("\n"); printf("val=%d\n",val); */ return val; } void solve() { queue <int> q; s=0; t=0; for(int i=0; i<n; i++) s+=a[i]; //printf("%d %d\n",s,t); memset(d,0x3f,(s+10)*sizeof(int)); memset(vis,0,(s+10)*sizeof(int)); d[s]=0; q.push(s); vis[s]=1; while(!q.empty()) { int u=q.front(); q.pop(); vis[u]=0; get_bug(u); //将十进制u转化为二进制保存在bug数组中 for(int i=1; i<=m; i++) //枚举所有补丁 { if(can_use(i)) //i补丁可以对u产生作用 { int v=get_newbug(i); //得到新状态的bug,已经转化为十进制,二进制保存在newbug数组中 if(d[u]+p[i].t < d[v]) { d[v]=d[u]+p[i].t; if(!vis[v]) { vis[v]=1; q.push(v); } } } } } return ; } int main() { int T=0; memset(a,0,sizeof(a)); a[0]=1; for(int i=1; i<=21; i++) a[i]=a[i-1]*2; //把2^i保存下来,以后方便使用不用多次计算 while(scanf("%d%d",&n,&m)) { if(!n &&!m) break; T++; for(int i=1; i<=m; i++) scanf("%d%s%s",&p[i].t,p[i].b0,p[i].b); solve(); printf("Product %d\n",T); if(d[0]!=INF) printf("Fastest sequence takes %d seconds.\n",d[0]); else printf("Bugs cannot be fixed.\n"); printf("\n"); } return 0; }