D115 最短路→负环 01分数规划+SPFA 算法 SP2885 WORDRING - Word Rings 单词环

D115 最短路→负环 01分数规划+SPFA 算法 SP2885 WORDRING - Word Rings 单词环_哔哩哔哩_bilibili

 

SP2885 WORDRING - Word Rings - 洛谷

给 n 个单词,每个单词考虑首尾的两个字符,如果能构成环,求环上的单词总长度与单词个数的比值的最大值。

思路

怎样建图?

将每个单词看做一个点,把能够相连两个单词之间连一条有向边,最多有 $10^5$ 个点,$10^{10}$ 条边,爆!

将每个单词看做一条边,从开头两个字符(看做点)向结尾两个字符(看做点)连有向边

这样点数缩小为 676个(26个字母取两个的排列数为 26*26),边数为 $10^5$ 条

image   image

怎样求解?

要求的答案为 $\frac {\sum len(i)} {\sum 1}$ 的最大值,分子表示 n 个单词的总长度,分母表示单词个数

最大化分数,显然是 01分数规划。我们二分一个 mid,然后判定图上是否存在一个环,该环是否满足

$\frac {\sum len(i)} {\sum 1} > mid$,即 $\sum (len(i)-1*mid)>0$,即 $\sum (1*mid-len(i))<0$

把 $1*mid-len(i)$ 看做新边权,即等效边权之和 $<0$,即图中存在一个 “负环”

每次二分跑一遍 SPFA 判是否存在负环,若存在说明 mid 偏小,则 $l=mid$,否则 $r=mid$

每个单词的长度范围为 [2,1000],所以比值的范围应为 (0,1000]

image

玄学优化:找环,栈比队列快!因为环上的点一旦入栈,就会出栈,顺藤摸瓜,扩展一圈找出环

相关板子:

A07【模板】01分数规划 - 董晓 - 博客园
D113【模板】最短路→负环 SPFA 算法 P2850 [USACO06DEC] Wormholes G 虫洞 - 董晓 - 博客园

 

// 二分+SPFA 算法 O(24*N*M)
#include<bits/stdc++.h>
using namespace std;

const int N=700,M=100010;
int h[N],to[M],ww[M],ne[M],idx;
void add(int a,int b,int c){
  to[++idx]=b,ww[idx]=c,ne[idx]=h[a],h[a]=idx;
}
int n;
double d[N];
int cnt[N],vis[N]; 

bool spfa(double mid){
  memset(d,0,sizeof d);
  memset(vis,0,sizeof vis);
  memset(cnt,0,sizeof cnt);
  stack<int> q; //栈比队列快
  for(int i=0; i<676; i++) q.push(i),vis[i]=true;
  
  while(!q.empty()){
    int u=q.top(); q.pop(); vis[u]=false;
    for(int i=h[u]; i; i=ne[i]){
      int v=to[i];
      double w=mid-ww[i]; //等效边权
      if(d[v]>d[u]+w){
        d[v]=d[u]+w;
        cnt[v]=cnt[u]+1;
        if(cnt[v]>=N) return true; //有负环
        if(!vis[v]) q.push(v),vis[v]=true;
      }
    }
  }
  return false;
}
int main(){
  char s[1010];
  while(scanf("%d",&n),n){
    idx=0; memset(h,0,sizeof h);
    for(int i=0; i<n; i++){ //以单词的两头为点,以单词为边建图
      scanf("%s",s);
      int len=strlen(s);
      if(len>=2){
        int left=(s[0]-'a')*26+s[1]-'a';
        int right=(s[len-2]-'a')*26+s[len-1]-'a';
        add(left,right,len);
      }
    }
    
    double l=0,r=1000;
    while(r-l>1e-4){
      double mid=(l+r)/2;
      if(spfa(mid)) l=mid;
      else r=mid;
    }
    if(l==0) puts("No solution.");
    else printf("%lf\n",l);
  }
}

 

posted @ 2026-03-25 14:31  董晓  阅读(38)  评论(0)    收藏  举报