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$ 条

怎样求解?
要求的答案为 $\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]

玄学优化:找环,栈比队列快!因为环上的点一旦入栈,就会出栈,顺藤摸瓜,扩展一圈找出环
相关板子:
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); } }
浙公网安备 33010602011771号