单词环(01分数规划)

题目描述

我们有 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lwz_159

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值