spfa的dfs版本poj2949

看了看《迭代求解的利器--------SPFA算法的优化与应用》(广东中山纪念中学 姜碧野)
讲到了spfa的dfs版本以及各种优化,很详细~

感觉这是个不错的题,以下是摘要,感觉作者写的太好了,没什么补充的了:
题意:
我们有n个(n<=100000)字符串,每个字符串都是由a~z的小写英文字母组成的字符串。如果字符串A的结尾两个字符刚好与字符串B的开头两字符相匹配,那么我们称A与B能相连(注意:A能与B相连不代表B能与A相连)。我们希望从给定的字符串中找出一些,使得他们首尾相接形成一个环串(一个串首尾相连也算)。我们想要使这个环串的平均长度最长。比如下例:
ababc
bckjaca
caahoynaab
第一个串能与第二个串相连,第二个串能和第三个串相连,第三个串能和第一个串相连,我们按照此顺序相连,便形成了一个环串,长度为5 + 7 + 10 = 22(重复部分算两次),总共使用了3个串,所以平均长度是22 / 3 ≈ 7.33
问题分析:
这道题综合了两种常见的问题:字符串的接龙以及平均值的最优化问题。对于前者,我们可以采取把单词看成边,把首尾字母组合看成点的方法。例如对于单词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
于是在二分答案后,我们把边的权值更新,问题就变成了查找图中是否存在一个正环。
可是如果使用常规的方法时间复杂度高达O(NMLogAns)其中N<=26*26,m<=100000
不单理论上无法承受,实际中也无法通过题目的数据,这便需要我们进行优化。
优化1略,优化2略,优化3:
当元素进入队列的总次数超过T(M+N)时,就判断图中存在正环。T可以根据题目特点自由选取,一般取2。
这个优化在一定程度上会影响算法的正确性,那还是否可取呢?其实在大部分情况,这个优化都是正确的,而且效果十分明显。虽然用正确性去换取时间,但这也正充分体现了SPFA灵活多样的优点,在实际应用中,我们往往可以根据题目的时间限制设置队列元素上限。
代码参考:
Bellman-ford算法 5000ms+

View Code
 1 #include <stdio.h>
 2 #include <string.h>
 3 #define N 750
 4 #define eps 1e-8
 5 #define inf -1000000000
 6 char ch[1005];
 7 int cnt,id;
 8 double mid;
 9 struct edge{
10     int from;
11     int to;
12     double w;
13 }e[100005];
14 int get(char a,char b){
15     return (a-'a')*26+b-'a';
16 }
17 void insert(int from,int to,double w,int id){
18     e[cnt].from=from;
19     e[cnt].to=to;
20     e[cnt++].w=w;
21 }
22 bool bellman(){
23     int i,j;
24     double dist[N];
25     for(i=0;i<710;i++) dist[i]=inf;
26     for(i=0;i<710;i++){
27         bool flag=0;//Make your code more optimized,Otherwise TLE! !
28         for(j=0;j<cnt;j++)
29             if(dist[e[j].from]+e[j].w-mid>dist[e[j].to]){
30                 flag=1;
31                 dist[e[j].to]=dist[e[j].from]+e[j].w-mid;
32             }
33         if(!flag) return 0;
34     }
35     for(int j=0;j<cnt;j++)
36         if (dist[e[j].from]!=inf&&dist[e[j].from]+e[j].w-mid>dist[e[j].to])
37             return 1;
38     return 0;
39 }
40 int main(){
41     bool flag;
42     int n,i,len;
43     double left,right;
44     while(scanf("%d",&n)&&n){
45         flag=0;
46         cnt=0;
47         left=right=0;
48         for(i=0;i<n;i++){
49             scanf("%s",ch);
50             len=strlen(ch);
51             insert(get(ch[0],ch[1]),get(ch[len-2],ch[len-1]),(double)len,0);
52             right=right>len?right:len;
53         }
54         while(right-left>=eps){
55             mid=(right+left)*0.5;
56             if (bellman()) {flag=1;left=mid;}
57             else right=mid;
58         }
59         if(!flag) puts("No solution.");
60         else
61         printf("%.2f\n",left);
62     }
63     return 0;
64 }

spfa的dfs算法 200ms+

View Code
 1 #include<cstring>
 2 #include<cstdio>
 3 #define N 200100
 4 #define eps 1e-8
 5 using namespace std;
 6 int n;
 7 int vis[900],k;
 8 int head[N],cnt;
 9 double dist[N],mid;
10 bool inStack[N];
11 int flag;
12 struct Edge{
13     int v,w,next;
14 }edge[N];
15 void init(){
16     memset(vis,0,sizeof(vis));
17     memset(head,-1,sizeof(head));
18     k=0;
19     cnt=0;
20 }
21 void addedge(int u,int v,int w){
22     edge[cnt].v=v;
23     edge[cnt].w=w;
24     edge[cnt].next=head[u];
25     head[u]=cnt++;
26 }
27 void dfs(int u){
28     int i;
29     inStack[u]=1;
30     for(i=head[u];i!=-1;i=edge[i].next){
31         int v=edge[i].v;
32         if(dist[v]+eps<dist[u]+(double)edge[i].w-mid){
33             dist[v]=dist[u]+(double)edge[i].w-mid;
34             if(inStack[v]){
35                 flag=1;
36                 return;
37             }
38             dfs(v);
39             if(flag)return;
40         }
41     }
42     inStack[u]=0;
43 }
44 bool judge(){
45     int i;
46     for(i=1;i<=n;i++)dist[i]=0;
47     memset(inStack,0,sizeof(inStack));
48     flag=0;
49     for(i=1;i<=n;i++){
50         dfs(i);
51         if(flag)return 1;
52     }
53     return 0;
54 }
55 int get(char a,char b){
56     return (a-'a')+(b-'a')*26;
57 }
58 int main(){
59     int i,j,len;
60     char str[1100];
61     while(scanf("%d",&n)&&n){
62         init();
63         for(i=1;i<=n;i++){
64             scanf("%s",str);
65             len=strlen(str);
66             int u=get(str[0],str[1]);
67             int v=get(str[len-2],str[len-1]);
68             if(vis[u]==0)
69                 vis[u]=++k;
70             if(vis[v]==0)
71                 vis[v]=++k;
72             addedge(vis[u],vis[v],len);
73         }
74         double right=1001,left=0;
75         bool flag=0;
76         while(right-left>eps){
77             mid=(right+left)/2;
78             if(judge()){
79                 flag=1;
80                 left=mid;
81             }
82             else right=mid;
83         }
84         if(flag==0) puts("No solution.");
85         else printf("%.2f\n",right+eps);
86     }
87     return 0;
88 }

由于Dfs总是能快速地使大量元素得到更新,所以一旦Dfs进入正环上的某个元素,便“很有可能”会通过正环又回到起点。初值为0也使Dfs少走了许多岔道,很大程度上Dfs的时间复杂度为O(M)级别。
这是因为查找正环和求最短路是有区别的。找环时初值应全部赋为0,这将会减少大量无用的计算,效率自然高了不少。有些读者便会怀疑赋初值为0的正确性,会不会由于初值相同而找不到正环,其实这是可以证明的。
首先假设初始时存在一个点s,从该点出发我们能找到正环(即以s为起点在环上走一圈,经过任意点时的dis[x]都大于0)。下面证明对环上某个点x的重赋值不会对正环的查找产生影响。
假设x在环上的前驱为y。本来在寻找正环时dis[y]+w(y,x)>dis[x],然后继续从x开始迭代。而如果dis[x]被重赋值了dis[x]’>=dis[y]+w(y,x),看似迭代到x时就停止了,但其实当x之前被赋为dis[x]’时,就已经可以从x开始继续在环上迭代了,也不需要再从y过渡到x。两者并无区别。依次类推,必然可以找到一个导致正环的起点。
而开始的假设则显然成立,否则我们可以把该正环分成若干段,每段的边权和<=0,与正环的前提矛盾,由此命题得证。

如果用裸的bellman-ford会超时,加上个小小的优化就好了~因为判断的是正环,所以如果连环的前提是可以连接~所以优化很简单~

posted @ 2013-02-28 21:42  _sunshine  阅读(1381)  评论(1编辑  收藏  举报