SPFA(判负环) + 二分(01分数规划) - Word Rings - POJ 2949

SPFA(判负环) + 二分(01分数规划) - Word Rings - POJ 2949

题意:

我们有 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 ≤ 1 0 5 1≤n≤10^5 1n105

输入样例:

3
intercommunicational
alkylbenzenesulfonate
tetraiodophenolphthalein
0

输出样例:

21.66

分析:

记 环 i 号 单 词 的 长 度 为 l e n i 。 记环i号单词的长度为len_i。 ileni

目 标 : 环 上 单 词 的 长 度 之 和 与 单 词 个 数 的 比 值 的 最 大 值 , 即 环 上 单 词 的 平 均 长 度 的 最 大 值 m a x ( ∑ l e n i ∑ 1 ) 。 目标:环上单词的长度之和与单词个数的比值的最大值,即环上单词的平均长度的最大值max(\frac{\sum len_i}{\sum 1})。 max(1leni)

比 较 直 观 的 01 分 数 规 划 问 题 。 比较直观的01分数规划问题。 01

二 分 答 案 m i d , 转 化 为 判 断 ∑ ( l e n i − m i d × 1 ) > 0 是 否 成 立 。 二分答案mid,转化为判断\sum (len_i-mid×1)>0是否成立。 mid(lenimid×1)>0

也 就 是 判 断 边 权 为 l e n i − m i d 的 图 中 是 否 存 在 正 环 。 也就是判断边权为len_i-mid的图中是否存在正环。 lenimid

二 分 的 区 间 应 当 是 [ 1 1000 , 1000 ] 。 二分的区间应当是[\frac{1}{1000},1000]。 [10001,1000]

若 最 终 结 果 小 于 1 1000 , 我 们 可 认 为 无 解 。 若最终结果小于\frac{1}{1000},我们可认为无解。 10001

难点:如何建图?

若 我 们 将 每 个 单 词 看 作 一 个 点 , 上 限 有 1 0 5 个 单 词 , 极 端 情 况 下 每 个 单 词 两 两 间 均 存 在 边 , 若我们将每个单词看作一个点,上限有10^5个单词,极端情况下每个单词两两间均存在边, 105

上 限 有 n × ( n − 1 ) ≈ 1 0 10 条 边 , 不 合 理 。 上限有n×(n-1)≈10^{10}条边,不合理。 n×(n1)1010

本 题 可 将 一 个 单 词 视 作 一 条 边 , 两 个 端 点 分 别 为 首 部 2 个 字 符 和 尾 部 两 个 字 符 , 边 权 为 单 词 的 长 度 。 本题可将一个单词视作一条边,两个端点分别为首部2个字符和尾部两个字符,边权为单词的长度。 2

我 们 将 首 位 4 个 字 符 , 分 别 哈 希 为 2 个 整 型 数 作 为 两 个 点 的 编 号 。 我们将首位4个字符,分别哈希为2个整型数作为两个点的编号。 42

2 位 字 符 至 多 26 × 26 = 676 种 可 能 , 即 整 个 图 至 多 有 676 个 点 。 2位字符至多26×26=676种可能,即整个图至多有676个点。 226×26=676676

于 是 能 够 建 立 不 多 于 676 个 点 , n 条 边 的 图 。 于是能够建立不多于676个点,n条边的图。 676n

s p f a 极 端 情 况 的 时 间 复 杂 度 为 O ( n m ) , n × m ≈ 676 × 1 0 5 = 6.76 × 1 0 7 。 spfa极端情况的时间复杂度为O(nm),n×m≈676×10^{5}=6.76×10^7。 spfaO(nm)n×m676×105=6.76×107

说明:

由 于 本 题 的 时 间 限 制 较 严 , 我 们 可 以 针 对 数 据 做 一 些 优 化 。 由于本题的时间限制较严,我们可以针对数据做一些优化。

当 经 过 的 点 的 数 量 超 过 一 个 较 大 值 时 , 我 们 就 可 认 为 是 存 在 环 的 。 当经过的点的数量超过一个较大值时,我们就可认为是存在环的。

比 如 本 题 中 至 多 676 个 点 , 若 更 新 点 的 次 数 超 过 10000 , 我 们 就 认 为 存 在 环 。 提 前 返 回 。 比如本题中至多676个点,若更新点的次数超过10000,我们就认为存在环。提前返回。 67610000

或 者 用 栈 来 代 替 队 列 , 同 样 能 够 较 快 的 判 断 出 负 环 。 或者用栈来代替队列,同样能够较快的判断出负环。

queue代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cstdio>

using namespace std;

const int N=676, M=1e5+10;

int m;
int e[M],ne[M],w[M],h[N],idx;
double dis[N];
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)
{
    memset(st,false,sizeof st);
    memset(cnt,0,sizeof cnt);
    
    queue<int> Q;
    for(int i=0;i<N;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 j=e[i];
            if(dis[j]<dis[u]+w[i]-mid)
            {
                dis[j]=dis[u]+w[i]-mid;
                cnt[j]=cnt[u]+1;
                if(cnt[j]>=N) return true;
                if(++Count>10000) return true;
                if(!st[j])
                {
                    st[j]=true;
                    Q.push(j);
                }
            }
        }
    }
    
    return false;
}

int main()
{
    while(~scanf("%d",&m),m)
    {
        memset(h,-1,sizeof h);
        idx=0;
        
        char str[1010];
        int left,right,len;
        for(int i=0;i<m;i++)
        {
            scanf("%s",str);
            len=strlen(str);
            if(len>=2)
            {
                left=(str[0]-'a')*26+str[1]-'a';
                right=(str[len-2]-'a')*26+str[len-1]-'a';
                add(left,right,len);
            }
        }
        
        double l=0,r=1000;
        while(r-l>1e-4)
        {
            double mid=(l+r)/2;
            if(check(mid)) l=mid;
            else r=mid;
        }
        
        if(r<1e-4) puts("No solution.");
        else printf("%lf\n",r); 
    }
    
    return 0;
}


stack代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<stack>
#include<cstdio>

using namespace std;

const int N=676, M=1e5+10;

int m;
int e[M],ne[M],w[M],h[N],idx;
double dis[N];
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)
{
    memset(st,false,sizeof st);
    memset(cnt,0,sizeof cnt);
    
    //stack<int> S;
    int stk[N],tt=0;
    for(int i=0;i<N;i++)
    {
        stk[tt++]=i;//S.push(i);
        st[i]=true;
    }
    
    int Count=0;
    while(tt)
    {
        int u=stk[--tt];//S.top();
        //S.pop();
        st[u]=false;
        
        for(int i=h[u];~i;i=ne[i])
        {
            int j=e[i];
            if(dis[j]<dis[u]+w[i]-mid)
            {
                dis[j]=dis[u]+w[i]-mid;
                cnt[j]=cnt[u]+1;
                if(cnt[j]>=N) return true;
                //if(++Count>10000) return true;
                if(!st[j])
                {
                    st[j]=true;
                    stk[tt++]=j;//S.push(j);
                }
            }
        }
    }
    
    return false;
}

int main()
{
    while(~scanf("%d",&m),m)
    {
        memset(h,-1,sizeof h);
        idx=0;
        
        char str[1010];
        int left,right,len;
        for(int i=0;i<m;i++)
        {
            scanf("%s",str);
            len=strlen(str);
            if(len>=2)
            {
                left=(str[0]-'a')*26+str[1]-'a';
                right=(str[len-2]-'a')*26+str[len-1]-'a';
                add(left,right,len);
            }
        }
        
        double l=0,r=1000;
        while(r-l>1e-4)
        {
            double mid=(l+r)/2;
            if(check(mid)) l=mid;
            else r=mid;
        }
        
        if(r<1e-4) puts("No solution");
        else printf("%lf\n",r); 
    }
    
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值