文章目录
题目分析
来源:acwing
分析:
如何建图?
这样建图。以样例举例。起点是前两个字母,终点是末尾两个字母,边权是字符串的长度。
我们求的是什么呢?
题目要求 Σ 边 权 Σ 1 ( 点 的 个 数 ) \frac{\Sigma{边权}}{\Sigma{1}(点的个数)} Σ1(点的个数)Σ边权的最大值,这种问题称为01分数规划,通俗点说,就是一堆的和除以一堆的和,要求比值最大。
我们可以通过二分来做,二分啥呢?就是对于一个环,二分一个mid值,判断是否满足 Σ 边 权 Σ 1 ( 点 的 个 数 ) ≥ m i d \frac{\Sigma{边权}}{\Sigma{1}(点的个数)} \geq mid Σ1(点的个数)Σ边权≥mid,然后我们就可以来不断更改二分的区间,直到找到 Σ 边 权 Σ 1 \frac{\Sigma{边权}}{\Sigma{1}} Σ1Σ边权的最大值。
思路确定了,那么具体如何实现呢?
Σ 边 权 Σ 1 ≥ m i d \frac{\Sigma{边权}}{\Sigma{1}} \geq mid Σ1Σ边权≥mid 由于这里的边都是正权边,所以可以移项,变成
Σ 边 权 − m i d × Σ 1 ≥ 0 {\Sigma{边权}} - mid \times{\Sigma{1}} \geq0 Σ边权−mid×Σ1≥0
将求和符号提出,亦等价于:
Σ
(
边
权
−
m
i
d
)
≥
0
{\Sigma{(边权} - mid )} \geq0
Σ(边权−mid)≥0
等价于 图中存在正环
下面就是默写spfa判正环。
当然建边的时候,需要把ab,ba这样的字母映射到一个整数(因为每一维都是26个英文字母的一个,这里用26 进制来做),是如下这样做的:
int left= (str[0] - 'a') * 26 + str[1] - 'a';
int right = (str[len -2 ] - 'a') * 26 + str[len - 1] - 'a';
ac代码
#include<bits/stdc++.h>
using namespace std;
const int N = 700, M = 100010;
int n;
int h[N], e[M], w[M], ne[M], idx;
double dist[N];
int q[N], 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, 0, sizeof st);
memset(cnt, 0, sizeof cnt);
int number = 26 * 26;
queue<int> q;
for(int i = 0; i < number ;i ++) q.push(i), st[i] = true;
int count = 0; // 防止超时,做的优化
while(q.size()){
auto t = q.front();
q.pop();
st[t] = false;
for(int i = h[t]; ~i; i = ne[i]){
int j = e[i];
if(dist[j] < dist[t] + w[i] - mid){
dist[j] = dist[t] + w[i] - mid;
cnt[j] = cnt[t] + 1;
if( ++ count > 2 * n) return true; // trick优化
if(cnt[j] >= N) return true;
if(!st[j]){
q.push(j),st[j] = true;
}
}
}
}
return false;
}
int main(){
char str[1010];
while(cin >> n, n){
memset(h, -1, sizeof h);
idx = 0;
for(int i = 0; i < n; i ++ ){
scanf("%s",str);
int len = strlen(str);
if(len >= 2){
int left= (str[0] - 'a') * 26 + str[1] - 'a';
int right = (str[len -2 ] - 'a') * 26 + str[len - 1] - 'a';
add(left, right, len);
}
}
if(!check(0)) puts("No solution");
else {
double l = 0, r = 1000;
while( r - l > 1e-4){
double mid = (l + r)/ 2;
if(check(mid)) l = mid;
else r = mid;
}
printf("%.2lf\n",r);
}
}
}
题目链接
https://www.acwing.com/problem/content/1167/