我们有 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≤10^5
输入样例:
3
intercommunicational
alkylbenzenesulfonate
tetraiodophenolphthalein
0
输出样例:
21.66
我们首先想一想,题目要求的是什么?
就是形成一个单词环,并且要求单词环的长度(也就是总的单词字母的长度)比上单词的个数的值最大
可以想到这就是01分数规划
设单词的个数是m,单词环的长度为len,结果是res,那么一定有len/m = res,我们要求res的值,可以做一个转化,一定满足
也就是可以把整个题转化成为一个求正环的问题,若是某些点出现了n+1次(也就是某些边出现了n次)那么就一定可以说明存在正环
但是需要注意的是,若是边的数量过多,用spfa需要的时间就特别长(spfa最坏的时间复杂度就是O(n*m))
那么我们可以考虑一种优化方法,若是循环次数大于3*n次(n是点的数量),那么我们就默认存在正环
现在我们处理一些细节:如何把单词映射成为点和边?我们可以这样思考:一个单词前两个字母和后两个字母是可以结合其他单词的,那么我们可不可以考虑把一个单词映射成为一条边?单词的开头和末尾作
为两个点来看,这样的话我们一共需要1e5条边,但是点的数量就很固定了,为什么?因为我们可以把a到z的26个字母映射成为数字,这样的话就一共只有26*26种组合,所以点的数量实际上不超过700
细节上我们可以特判下正环不存在的情况,也就是单词环的长度比上单词的个数为0,也就是说根本不存在环,若是我们用0做一遍spfa,返回值仍然还是false的话就说明根本就不存在环,这时候直接做一次spfa然后就可以直接退出了
其他情况下我们就需要二分了,边界值一个是0一个是1000,每次二分出一个答案,若是符合题意的话就左边界等于mid值,反之让右边界等于mid值,因为我们要求的值再区间右侧嘛
在做spfa的时候我们还要注意一点:由于边的个数过多,因此我们需要多加上一个判断因素count,若是count的值大于3*n,也就是循环次数是点数的好几倍,我们就可以认为有正环存在,然后就可以返回true了
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 100010;
int n;
int h[N],ne[N],e[N],w[N],idx;
double dist[N];
int q[N],cnt[N],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)
{
memset(st,0,sizeof st);
memset(cnt,0,sizeof cnt);
queue<int>q;
for(int i=0;i<676;i++)
{
st[i] = 1;
q.push(i);
}
int count = 0;
while(q.size())
{
int t = q.front();
q.pop();
st[t] = 0;
for(int i=h[t];~i;i=ne[i])
{
int j = e[i];
if(dist[j]<dist[t]+w[i]-mid)
{
dist[j] = dist[t]+w[i]-mid;
cnt[j] = cnt[t] + 1;
if(++count>10000) return 1;
if(cnt[j]>=N) return 1;
if(!st[j])
{
st[j] = 1;
q.push(j);
}
}
}
}
return 0;
}
int main()
{
char str[1010];
while(scanf("%d",&n),n)
{
memset(h,-1,sizeof h);
idx = 0; //这是为了避免爆数组
for(int i=0;i<n;i++)
{
scanf("%s",str);
int len = strlen(str); //每个单词都是一条边
if(len>=2)
{
int left = (str[0]-'a')*26+str[1]-'a';
int right = (str[len-2]-'a')*26+str[len-1]-'a';
add(left,right,len);
}
}
if(!check(0)) cout<<"No solution"<<endl;
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);
}
}
return 0;
}
要加油哇!!!