AcWing 1165 单词环

题目描述:

我们有 n 个字符串,每个字符串都是由 a∼z 的小写英文字母组成的。

如果字符串 A 的结尾两个字符刚好与字符串 B 的开头两个字符相匹配,那么我们称 A 与 B 能够相连(注意:A 能与 B 相连不代表 B 能与 A 相连)。

我们希望从给定的字符串中找出一些,使得它们首尾相连形成一个环串(一个串首尾相连也算),我们想要使这个环串的平均长度最大。

如下例:

ababc
bckjaca
caahoynaab

第一个串能与第二个串相连,第二个串能与第三个串相连,第三个串能与第一个串相连,我们按照此顺序相连,便形成了一个环串,长度为 5+7+10=22(重复部分算两次),总共使用了 3 个串,所以平均长度是 22 / 3≈7.33。

输入格式

本题有多组数据。

每组数据的第一行,一个整数 n,表示字符串数量;

接下来 n 行,每行一个长度小于等于 1000 的字符串。

读入以 n=0 结束。

输出格式

若不存在环串,输出”No solution”,否则输出最长的环串的平均长度。

只要答案与标准答案的差不超过 0.01,就视为答案正确。

数据范围

1≤n≤100000

输入样例:

3
intercommunicational
alkylbenzenesulfonate
tetraiodophenolphthalein
0

输出样例:

21.66

分析:

本题同样是考察01分数规划,只不过难度较上题有所提升。首先需要考虑的是如何建图,如果常规的用每个字符串作为图中的节点,最多会有10w个节点,边数也可能高达100亿级别,显然难以承受,而且以字符串作为节点,还需要挨个比较每个字符串的末尾两个字符与其它字符串的开头两个字符是否相等,这也将耗费大量的时间。我们知道,有小写字母构成的两个字符最多有26 * 26 = 676种可能,而这十万字符串中间的内容并不重要,我们只关心每个字符串的开始两个字符、末尾两个字符以及字符串的长度。这样我们读入一个字符串时,将其开头长度为2的字符串作为一个节点,末尾长度为2的字符串作为另一个节点,两个节点间有向边的长度就是该字符串的长度。这种巧妙的建图方式不仅使得节点数骤减,边数不超过限制10w,更方便的是每条边都象征着存在一个字符串开头字符串是a,末尾字符串是b,字符串长度是c。如果b指向另一个点d,abd就自然的连接起来了,并且连接后字符串的长度就是边权之和,非常方便。既然节点最多有26*26种可能性,那么就可以将长度为2的字符串进行哈希映射到0到675的区间中,比如aa就映射到编号为0的节点,这样本题的图就建好了。

第二步就是按照01分数规划的解题思路推公式,环串的平均长度最大,等价于∑si / ∑1最大,其中si表示环中各边的长度,∑1就是环上的边数,要判断对于某个mid是否有∑si / ∑1 >= mid,只需要∑si  >= ∑1*mid,即∑(mid - si)  <= 0即可,即边权为mid - si的图中存在负权回路,问题就进一步转化为了spfa求负权回路问题了。

最后本题时间卡得很紧,如果要等待出现一条路径涉及边的条数达到676再确定存在负环,就会超时,这里就要用到玄学优化了,一般经验值取节点数的两倍,这里边比较多就取100000,即被松弛的总次数达到100000时就按照经验判定为存在负环,结束spfa算法。也可以将spfa是否中的队列换成栈,在实现上相当于每次都从队尾取元素,这样一来,一旦存在环,就会很快的去松弛环上的下一个节点,更快的结束算法。另外,对于不存在负权的情况,只需要判断下mid = 0的时候是否合法即可,∑si / ∑1 >= mid = 0,等价于∑s >= 0,也就是存在一个单词环满足题目要求即可,无解的情况就是无法构造出环。总的代码如下:

#include 
#include 
#include 
using namespace std;
const int N = 700,M = 100005;
int idx,h[N],e[M],w[M],ne[M];
int n,cnt[N],q[N];
double d[N];
bool st[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 k){
    memset(d,0,sizeof d);
    memset(cnt,0,sizeof cnt);
    memset(st,false,sizeof st);
    int hh = 0,tt = 0;
    for(int i = 0;i < 676;i++){
        q[tt++] = i;
        st[i] = true;
    }
    int count = 0;
    while(hh != tt){
        int u = q[hh++];
        if(hh == N) hh = 0;
        st[u] = false;
        for(int i = h[u];~i;i = ne[i]){
            int j = e[i];
            if(d[u] + k - w[i] < d[j]){
                d[j] = d[u] + k - w[i];
                cnt[j] = cnt[u] + 1;
                if(++count > 10000 || cnt[j] >= 676)   return true;
                if(!st[j]){
                    q[tt++] = j;
                    if(tt == N) tt = 0;
                    st[j] = true;
                }
            }
        }
    }
    return false;
}
int main(){
    char s[1005];
    while(scanf("%d",&n),n){
        memset(h,-1,sizeof h);
        idx = 0;
        for(int i = 0;i < n;i++){
            scanf("%s",s);
            int n = strlen(s);
            if(n < 2)   continue;
            int a = (s[0] - 'a') * 26 + s[1] - 'a';
            int b = (s[n - 2] - 'a') * 26 + s[n - 1] - 'a';
            add(a,b,n);
        }
        if(!check(0))   puts("No solution");
        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",l);
        }
    }
    return 0;
}

 

你可能感兴趣的:(算法提高课,01分数规划,spfa求负环)