AcWing 1165. 單詞環(01分數規劃)

題幹:

我們有 n 個字符串,每個字符串都是由 a∼z 的小寫英文字母組成的。
如果字符串 A 的結尾兩個字符剛好與字符串 B 的開頭兩個字符相匹配,那麼我們稱 A 與 B 能夠相連(注意:A 能與 B 相連不代表 B 能與 A 相連)。
我們希望從給定的字符串中找出一些,使得它們首尾相連形成一個環串(一個串首尾相連也算),我們想要使這個環串的平均長度最大。
如下例:
ababc
bckjaca
caahoynaab
第一個串能與第二個串相連,第二個串能與第三個串相連,第三個串能與第一個串相連,我們按照此順序相連,便形成了一個環串,長度爲 5+7+10=225+7+10=22(重複部分算兩次),總共使用了 3 個串,所以平均長度是 22/37.3322/3≈7.33。

輸入格式
本題有多組數據。
每組數據的第一行,一個整數 n,表示字符串數量;
接下來 n 行,每行一個長度小於等於 1000 的字符串。
讀入以 n=0 結束。
輸出格式
若不存在環串,輸出”No solution”,否則輸出最長的環串的平均長度。
只要答案與標準答案的差不超過 0.01,就視爲答案正確。

數據範圍
1≤n≤10510^5
輸入樣例:
3
intercommunicational
alkylbenzenesulfonate
tetraiodophenolphthalein
0
輸出樣例:
21.66

思路:

根據題意,我們關心的只是一個字符串的開頭兩位和結尾兩位,那可以將這兩個字符映射成一個數(字符串hash的思想),這樣A串能與 B串相連就轉換成了A‘點與B’點相連,也就是圖論問題。
然後考慮環串的平均長度最大,即leni1\frac{\sum_{}len_i} {\sum_{}1}最大,這就是一個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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章