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;
}
posted @ 2012-07-13 17:28 Because Of You Views(...) Comments(...) Edit 收藏