Word Rings

https://loj.ac/problem/10082

题目描述

  给出若干字符串,如果A的末2个字符和B的首2个字符相同,那么称A和B相连,求从给定的字符串中找出一条环串使串的平均长度最短。

思路

  首先我们考虑建图,显然,以字符串为点的图难以实现,而两个字符最多只有26×26个节点,那么字符串就是连接两个节点的边,其边权即为串的长度。接下来我们考虑如何求串的平均长度。

  假设串的平均长度为average,那么我们有等式:

  (s1-average)+(s2-average)...(sn-average)=0。

  由此我们可以知道这个average具有单调性,若把每条边的边权减去average后存在一个正环,那么就把二分的答案减小。

  不过如果用常规的spfa求环,很可能会T,被卡到上界。我们考虑这里并不需要每个点的最短路,因此完全可以不用bfs,而用dfs。而为何能用dfs,因为查找正环和最短路不一样,我们不用正确的最短路,而是一个点的被更新次数,并且dfs很可能通过访问从一个点出发重新到达一个点,因此效率相比较bfs大大提高了。我们还可以进行优化,如果dis[x]>最大边权*边数,显然就出现了正环。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+10;
const double eps=1e-4;
int nxt[N],head[N],to[N],vis[N],tot;
double dis[N],w[N],maxe;
int f,alpha[N];
char s[1100];
void add_edge(int x,int y,double v)
{
    nxt[++tot]=head[x];
    head[x]=tot;
    to[tot]=y;
    w[tot]=v;
}
void spfa(int u,int h,double aver)
{
    if(f)return ;
    vis[u]=h;
    for(int i=head[u];~i;i=nxt[i])
    {
        int v=to[i];
        if(dis[u]+w[i]-aver>dis[v])
        {
            dis[v]=dis[u]+w[i]-aver;
            if(dis[v]>maxe){f=1;return ;}//最大边权 
            if(!vis[v])spfa(v,h,aver);
            if(vis[v]==h){f=1;return ;}//已在dfs中访问过 
        }
    }
    vis[u]=0;
}
bool check(double aver)
{
    memset(vis,0,sizeof(vis));
    memset(dis,0,sizeof(dis));
    f=0;
    for(int i=1;i<=tot;i++)
    {
        spfa(i,i,aver);
        if(f)break ;
    }
    return f;
}
int main() 
{
    int n,idx;
    while(~scanf("%d",&n))
    {
        tot=0;maxe=0;idx=0;
        memset(alpha,0,sizeof(alpha));
        memset(head,-1,sizeof(head));
        if(n==0)break ;
        for(int i=1;i<=n;i++)
        {
            scanf(" %s",s);
            int len=strlen(s);
            maxe=max(maxe,(double)len);
            int x=(s[0]-'a')*26+s[1]-'a';
            int y=(s[len-2]-'a')*26+s[len-1]-'a';
            if(!alpha[x])alpha[x]=++idx;
            int id1=alpha[x];
            if(!alpha[y])alpha[y]=++idx;
            int id2=alpha[y];
            add_edge(id1,id2,len);
        }
        maxe=maxe*n;
        double l=0,r=1000,ans=-1;
        while(r-l>eps)
        {
//            cout<<r<<' '<<l<<endl;
            double mid=(l+r)/2;
            if(check(mid))ans=mid,l=mid;
            else r=mid;
        }
        if(ans!=-1)printf("%.3lf\n",ans);
        else printf("No solution\n");
    }
    return 0;
}

 

posted @ 2019-10-20 14:03  fbz  阅读(195)  评论(0编辑  收藏  举报