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+
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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+
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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会超时,加上个小小的优化就好了~因为判断的是正环,所以如果连环的前提是可以连接~所以优化很简单~