spfa算法的优化及应用 poj 2949
这道题综合了两种常见的问题:字符串的接龙以及平均值的最优化问题。对于前者,我们可以采取把单词看成边,把首尾字母组合看成点的方法。例如对于单词ababc就是点”ab”向点”bc”连一条长度为5的边。这样问题的模型变得更加清晰,规模也得到减小。那么原问题就可以转化成在此图中找一个环,使得环上边权的平均值最大。对于这种问题,我们有很经典的解决方法:
由于Average=(E1+E2+…..+Ek)/K
所以Average*K=E1+E2+……+Ek
即(E1-Average)+(E2-Average)+….+ (Ek-Average)=0
另外注意到上式中的等于号可以改写为小于等于,那么我们可以二分答案Ans,然后判断是否存在一组解满足(E1+E2+…..+Ek)/K>Ans,即判断
(E1- Ans)+(E2- Ans)+….+ (Ek- Ans)>0
于是在二分答案后,我们把边的权值更新,问题就变成了查找图中是否存在一个正环。
以上摘自国家队论文《spfa算法的优化及应用》
知道了以上方法后,主要要解决的问题就是如何找环了
在试了论文中的多种优化方法后,最终还是臣服于将spfa写成dfs,直接将2000ms的代码加速到了200ms,太匪夷所思了
仔细思考后发现,当spfa在解决最短路径问题时用bfs或者dfs区别可能不大(但有的题目的数据比较***钻,用可能用dfs(栈)更加高效)
但是在需要找环的时候,dfs的做法变的非常高效,直接从最近被更新的点出发继续松弛操作,很快就可以找到环了
也不用等到入队超过n次才判断出现环
最快冲到了235ms,这已经是我的极限了,虽然没挤进第一版,不过还是收获蛮大,对一个算法的优化永远都没有止境
以下是2000多ms的算法
View Code
#include<cstdio> #include<map> #include<queue> #include<stack> #include<cstring> #include<algorithm> using namespace std; const int N = 1010; const int M = 100010; const double eps = 1e-3; const double inf = 1000000000; struct EDGE{ int v,next; double w; }edge[M],TE[M]; int head[N]; int E,tot; stack<int> ST; void add_edge(int a,int b,double w){ edge[E].v=b; edge[E].w=w; edge[E].next=head[a]; head[a]=E++; } bool vis[N]; double dis[N]; int in[N]; bool PC;//正环 double MaxEdge; void spfa(double aver,int n){ while(!ST.empty()) ST.pop(); fill(vis,vis+n+1,false); fill(dis,dis+n+1,0); fill(in,in+n+1,0); for(int i=1;i<=n;i++) ST.push(i),vis[i]=true; while(!ST.empty()){ int u=ST.top();ST.pop();vis[u]=false; for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].v; if(dis[u]+edge[i].w-aver>dis[v]){ dis[v]=dis[u]+edge[i].w-aver; if(dis[v]>MaxEdge){ PC=true;return ;} if(!vis[v]){ vis[v]=true; ST.push(v); in[v]++; if(in[v]>n+1){ PC=true; return ; } } } }//end of for }// end of while } bool solve(double aver){ PC=false; spfa(aver,tot); return PC; } char s[M]; int flag[3010]; int main(){ int n; while(scanf("%d",&n),n){ E=0;tot=0;MaxEdge=0; memset(head,-1,sizeof(head)); fill(flag,flag+3000,0); for(int i=0;i<n;i++){ scanf("%s",s); int len=strlen(s); if(len>MaxEdge) MaxEdge=len; int a=(s[0]-'a')*26+s[1]-'a'; int b=(s[len-2]-'a')*26+s[len-1]-'a'; if(!flag[a]) flag[a]=++tot; int id1=flag[a]; if(!flag[b]) flag[b]=++tot; int id2=flag[b]; add_edge(id1,id2,(double)len); } MaxEdge*=n; double l=0,r=1000,best=-1,mid; while(l+eps<=r){ mid=(l+r)/2; if(solve(mid)){ best=mid; l=mid; } else r=mid; } if(best!=-1) printf("%.3lf\n",best); else printf("No solution.\n"); } return 0; }
下面是200多ms的算法
View Code
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N = 677; const int M = 100010; const double eps = 1e-3; const double inf = 1000000000; struct EDGE{ int v,next; double w; }edge[M],TE[M]; int head[N],E,tot; void add_edge(int a,int b,double w){ edge[E].v=b; edge[E].w=w; edge[E].next=head[a]; head[a]=E++; } int vis[N]; double dis[N]; int in[N]; bool PC;//正环 double MaxEdge,aver; void spfa(int u,int h,double aver){ if(PC) return ; vis[u]=h; for(int i=head[u];i!=-1;i=edge[i].next){ int v=edge[i].v; if(dis[u]+edge[i].w-aver>dis[v]){ dis[v]=edge[i].w+dis[u]-aver; if(dis[v]>MaxEdge) {PC=true;return ;} if(!vis[v]) spfa(v,h,aver);if(PC) return ; else if(vis[v]==h){ PC=true; return ; } } } vis[u]=0; } bool solve(double aver){ PC=false; fill(dis,dis+tot+1,0); fill(vis,vis+tot+1,0); for(int i=1;i<=tot;i++){ spfa(i,i,aver); if(PC) break; } return PC; } char s[M]; int flag[800]; int main(){ int n; while(scanf("%d",&n),n){ E=0;tot=0;MaxEdge=0; fill(head,head+700,-1); fill(flag,flag+700,0); for(int i=0;i<n;i++){ scanf("%s",s); int len=strlen(s); if(len>MaxEdge) MaxEdge=len; int a=(s[0]-'a')*26+s[1]-'a'; int b=(s[len-2]-'a')*26+s[len-1]-'a'; if(!flag[a]) flag[a]=++tot; int id1=flag[a]; if(!flag[b]) flag[b]=++tot; int id2=flag[b]; add_edge(id1,id2,(double)len); } MaxEdge*=n; double l=0,r=1000,best=-1,mid; while(l+eps<r){ mid=(l+r)/2; if(solve(mid)){ best=mid; l=mid; } else r=mid; } if(best!=-1) printf("%.3f\n",best); else printf("No solution.\n"); } return 0; }