单词环

我们有 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的值,可以做一个转化,一定满足 \sum len - res*n>0

也就是可以把整个题转化成为一个求正环的问题,若是某些点出现了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;
} 

要加油哇!!!

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值