求负环模板题 + 小建图技巧。
题意:
给几个字符串 如果A的后两个字母等于B的前两个字母,可连起来,每个字符串的权值等于它的长度,最后形成环的总长度除字符串的个数,要求最大。
题解:
首先建图就不说了, 01分数规划问题就是二分,推公式
∑len/s 要求最大,我们假设 ∑len/s 当前大于二分的mid 那么
∑len - mid*s > 0 即为公式推导, 作为二分的依据, 除此之外,只需要按照求负环的sofa算法套模板就可以了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 700, M = 100100;
ll n;
ll h[N], w[M], ne[M], e[M], idx;
double dis[N];
bool st[N];
ll q[N], nod[N];
bool check(double mid)
{
memset(st, 0, sizeof st);
memset(nod , 0, sizeof nod);
int hh=0, tt = 0;
for (int i = 0; i < 676; i ++ ) // 将所有的点全部入队,当有点入队次数大于n次,代表有负环,但是本题可能会超时,所以我们用个小技巧,当循环次数大于10000次时,我们猜测他有负环,可以ac
{
q[tt ++ ] = i;
st[i] = true;
}
int count = 0;
while (hh != tt)
{
int t = q[hh ++ ];
if (hh == N) hh = 0;
st[t] = false;
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (dis[j] < dis[t] + w[i] - mid)// 如果到新点的距离的这条边符合公式推导的情况
{
dis[j] = dis[t] + w[i] - mid;
nod[j] = nod[t] + 1;
if ( ++ count > 10000) return true; // 经验上的trick
if (nod[j] >= N) return true;
if (!st[j])
{
q[tt ++ ] = j;
if (tt == N) tt = 0;
st[j] = true;
}
}
}
}
return false;
}
void add(ll a, ll b, ll c)
{
e[idx] = b; w[idx] = c; ne[idx] = h[a]; h[a] = idx ++;
}
int main()
{
while(cin >> n, n)
{
memset(h, -1, sizeof h);
idx = 0;
for(int i = 1; i <= n; i ++)
{
string s;
cin >> s;
ll len = s.size();
if(len < 2) continue;
ll l = (s[0]-'a')*26 + s[1]-'a';
ll r = (s[len-2] - 'a')*26 + s[len-1]-'a';
add(l, r, 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("%lf\n", r);
}
}
return 0;
}