spfa算法的优化及应用 poj 2949

来源:互联网 发布:excel跨工作簿引用数据 编辑:程序博客网 时间:2024/05/21 08:37

这道题综合了两种常见的问题:字符串的接龙以及平均值的最优化问题。对于前者,我们可以采取把单词看成边,把首尾字母组合看成点的方法。例如对于单词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;}
原创粉丝点击