题目描述
我们有 n 个字符串,每个字符串都是由 a∼z 的小写英文字母组成的。
如果字符串 A 的结尾两个字符刚好与字符串 B 的开头两个字符相匹配,那么我们称 A 与 B 能够相连(注意:A 能与 B 相连不代表 B 能与 A 相连)。
我们希望从给定的字符串中找出一些,使得它们首尾相连形成一个环串(一个串首尾相连也算),我们想要使这个环串的平均长度最大。
如下例:
ababc
bckjaca
caahoynaab
第一个串能与第二个串相连,第二个串能与第三个串相连,第三个串能与第一个串相连,我们按照此顺序相连,便形成了一个环串,长度为 5+7+10=22(重复部分算两次),总共使用了 3 个串,所以平均长度是 223≈7.33。
输入格式
本题有多组数据。
每组数据的第一行,一个整数 n,表示字符串数量;
接下来 n 行,每行一个长度小于等于 1000 的字符串。
读入以 n=0 结束。
输出格式
若不存在环串,输出”No solution”,否则输出最长的环串的平均长度。
只要答案与标准答案的差不超过 0.01,就视为答案正确。
数据范围
1≤n≤105
输入样例
3
intercommunicational
alkylbenzenesulfonate
tetraiodophenolphthalein
0
输出样例
21.66
题目分析
首先本题的第一个难点是如何建图?
一种比较直观的建图方式是:将每个单词看成是一个点,如果两个单词之间可以相连,则在两个单词之间建一条边。那么次数点的数量最多是105,边的数量最大是1010。这样的数量级是不可接受的,因此我们要考虑一种别的建图方式。
我们可以将每个单词看成是一条边,而单词开头和结尾的两个字母看成是边两头的点,边的权值为这个单词的长度。这样点数最多为676个(26*26),而边数也减为了105个。
因为是求最大值,该答案具有单调性,因为我们也可以用二分求解。二分的具体流程就不细说了,这里只讲一下check函数要怎么写。
假设当前的二分值为mid,边权为w[i]。
如果ans>=mid,那么就说明图中存在一个单词环的长度sum(w[i])/环上单词个数sum(1)>=mid。下一步就是给该公式进行一下变形:
sum(w[i])/sum(1)>=mid
sum(w[i])-sum(1)*mid>=0
sum(w[i]-1*mid)>=0 => 图中是否存在一个正环使得该不等式成立
因此我们的check函数只需要写spfa判断一下是否存在正环即可。
注意:这道题的数据范围比较大,要一个点的迭代次数大于等于n需要的时间比较长,对这道题来说会超时。因此我们需要加一个玄学优化:一般来说,如果spfa算法中总迭代次数大于2*n(点数),那么说明这个图中存在环。那么我们就可以直接认定为该图存在环并退出函数。
代码如下
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <map>
#include <queue>
#include <stack>
#include <vector>
#include <set>
#include <algorithm>
#define LL long long
#define ULL unsigned long long
#define PII pair<int,int>
#define x first
#define y second
using namespace std;
const int N=7e2+5,M=1e5+5,INF=0x3f3f3f3f;
int n;
int h[N],e[M],w[M],ne[M],idx;
double dist[N]; //记录sum(w[i]-mid)的值
int cnt[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 mid) //检查mid的值是否合法
{
memset(st,0,sizeof st);
memset(cnt,0,sizeof cnt);
memset(dist,0,sizeof dist); //因为要求的是sum(w[i]-mid)的最大值,因此可以将dist初始化为0
queue<int> q;
for(int i=0;i<676;i++) //将所有点放入队列中
{
q.push(i);
st[i]=true;
}
int count=0; //记录总的迭代次数
while(q.size())
{
int u=q.front();
q.pop();
st[u]=false;
for(int i=h[u];~i;i=ne[i])
{
int v=e[i];
if(dist[v]<dist[u]+w[i]-mid)
{
dist[v]=dist[u]+w[i]-mid;
cnt[v]=cnt[u]+1;
if(cnt[v]>=N) return true; //如果某一点的迭代次数大于等于n,说明存在正环
if(++count>2*n) return true; //如果总的迭代次数大于2*n,我们可以认为存在正环
if(!st[v])
{
q.push(v);
st[v]=true;
}
}
}
}
return false;
}
int main()
{
while(scanf("%d",&n),n)
{
idx=0;
memset(h,-1,sizeof h);
for(int i=0;i<n;i++) //建图
{
char s[1005];
scanf("%s",s);
int len=strlen(s); //求该字符串的长度
if(len>=2) //如果字符串长度不足2,可以直接忽略
{
int a=(s[0]-'a')*26+s[1]-'a'; //将两个字母映射为一个26进制的数字来做为点
int b=(s[len-2]-'a')*26+s[len-1]-'a';
add(a,b,len);
}
}
double l=0,r=1000; //浮点数二分的模板
while(r-l>1e-5)
{
double mid=(l+r)/2;
if(check(mid)) l=mid; //如果mid值合法,则看看是否存在更大的解
else r=mid; //否则说明mid值过大,减小mid值
}
if(l>1e-4) printf("%lf\n",l); //l等于0(mid等于0)时,说明无解
else puts("No solution");
}
return 0;
}