題幹:
我們有 n 個字符串,每個字符串都是由 a∼z 的小寫英文字母組成的。
如果字符串 A 的結尾兩個字符剛好與字符串 B 的開頭兩個字符相匹配,那麼我們稱 A 與 B 能夠相連(注意:A 能與 B 相連不代表 B 能與 A 相連)。
我們希望從給定的字符串中找出一些,使得它們首尾相連形成一個環串(一個串首尾相連也算),我們想要使這個環串的平均長度最大。
如下例:
ababc
bckjaca
caahoynaab
第一個串能與第二個串相連,第二個串能與第三個串相連,第三個串能與第一個串相連,我們按照此順序相連,便形成了一個環串,長度爲 5+7+10=225+7+10=22(重複部分算兩次),總共使用了 3 個串,所以平均長度是
輸入格式
本題有多組數據。
每組數據的第一行,一個整數 n,表示字符串數量;
接下來 n 行,每行一個長度小於等於 1000 的字符串。
讀入以 n=0 結束。
輸出格式
若不存在環串,輸出”No solution”,否則輸出最長的環串的平均長度。
只要答案與標準答案的差不超過 0.01,就視爲答案正確。
數據範圍
1≤n≤
輸入樣例:
3
intercommunicational
alkylbenzenesulfonate
tetraiodophenolphthalein
0
輸出樣例:
21.66
思路:
根據題意,我們關心的只是一個字符串的開頭兩位和結尾兩位,那可以將這兩個字符映射成一個數(字符串hash的思想),這樣A串能與 B串相連就轉換成了A‘點與B’點相連,也就是圖論問題。
然後考慮環串的平均長度最大,即最大,這就是一個01分數規劃的模型。
但由於串的個數比較多,可能使SPFA判環的效率降低,所以我們需要優化下效率。
①經驗做法
當總入隊次數超過總點數十倍左右大概就是有環了
//老大的經驗做法
//當總入隊次數超過總點數十倍左右大概就是有環了
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=700,M=100010;
double dis[N];
int cnt[N],q[N];
int h[N],e[M],w[M],ne[M],idx;
bool vis[N];
void add(int a,int b,int c){
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
bool check(double mid){
int tt=0,hh=0;
memset(vis,false,sizeof(vis));
for(int i=0;i<676;i++){
dis[i]=cnt[i]=0;
vis[i]=true;
q[tt++]=i;
}
int count=0;
while(hh!=tt){
int t=q[hh++];
vis[t]=false;
if(hh==N) hh=0;
for(int i=h[t];~i;i=ne[i]){
int j=e[i];
if(dis[j]<dis[t]+w[i]-mid){
dis[j]=dis[t]+w[i]-mid;
cnt[j]=cnt[t]+1;
if(++count>10000) return true;
if(cnt[j]>=N) return true;
if(!vis[j]){
q[tt++]=j;
if(tt==N) tt=0;
vis[j]=true;
}
}
}
}
return false;
}
int main(){
int t;
char ch[1010];
while(scanf("%d",&t),t){
memset(h,-1,sizeof(h));
idx=0;
while(t--){
scanf("%s",ch);
int len=strlen(ch);
if(len>=2){
int left=(ch[0]-'a')*26+ch[1]-'a';
int right=(ch[len-2]-'a')*26+ch[len-1]-'a';
add(left,right,len);
//printf("%d %d\n",left,right);
}
}
if(!check(0))
printf("No solution\n");
else{
double l=0,r=1000;
while(r-l>1e-4){
double mid=(l+r)/2;
if(check(mid))
l=mid;
else
r=mid;
//printf("%lf\n",r);
}
printf("%lf\n",r);
}
}
return 0;
}
②SPFA的優化(隊列改棧)
由先進先出的隊列改爲先進後出的棧
//SPFA的隊列改棧
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=700,M=100010;
double dis[N];
int cnt[N],st[N];
int h[N],e[M],w[M],ne[M],idx;
bool vis[N];
void add(int a,int b,int c){
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
bool check(double mid){
int tt=0,hh=0;
memset(vis,false,sizeof(vis));
for(int i=0;i<676;i++){
dis[i]=cnt[i]=0;
vis[i]=true;
st[++tt]=i;
}
while(tt){
int t=st[tt--];
vis[t]=false;
for(int i=h[t];~i;i=ne[i]){
int j=e[i];
if(dis[j]<dis[t]+w[i]-mid){
dis[j]=dis[t]+w[i]-mid;
cnt[j]=cnt[t]+1;
if(cnt[j]>=N) return true;
if(!vis[j]){
st[++tt]=j;
if(tt==N) tt=0;
vis[j]=true;
}
}
}
}
return false;
}
int main(){
int t;
char ch[1010];
while(scanf("%d",&t),t){
memset(h,-1,sizeof(h));
idx=0;
while(t--){
scanf("%s",ch);
int len=strlen(ch);
if(len>=2){
int left=(ch[0]-'a')*26+ch[1]-'a';
int right=(ch[len-2]-'a')*26+ch[len-1]-'a';
add(left,right,len);
//printf("%d %d\n",left,right);
}
}
if(!check(0))
printf("No solution\n");
else{
double l=0,r=1000;
while(r-l>1e-4){
double mid=(l+r)/2;
if(check(mid))
l=mid;
else
r=mid;
//printf("%lf\n",r);
}
printf("%lf\n",r);
}
}
return 0;
}