最近做了好多二分+最短路的题啊
看到环之后就能想到SPFA判环了,但是具体怎么建边呢?我们把一个字符串的前两个字符看作一个节点,后两个字符看作一个节点,前面的向后面的连边(有向),边权为这个字符串的长度,这样,就把图建了起来,并且符合我们跑最长路找环的思想。但是,我们总不可能把所有环找出来看最大值吧,所以我们换种思路,来枚举答案,看是否找得到满足我们枚举的答案的环。但是,枚举太耗费时间了,这时,我们就引入二分。显然,答案越大越容易找到满足条件的环,所以,有了单调性,就可以二分了。
代码(细节挺多的...):
#include <bits/stdc++.h>
using namespace std;
int n;
double l , r , mid;
double eps = 1e-6;
double dis[200010];
int vis[200010];
vector<pair<int , double> > e[200010];
int id(char x , char y){ //注意这里的转化成数字的方式,决定了节点应该如何枚举
//因为我直接转化的,没有用hash之类的去使节点数固定,所以枚举节点时直接到3000(其实1000就行,一开始没算)
return (x - 'a') * 26 + y - 'a' + 1;
}
void add(int x , int y , double z){
e[x].push_back(make_pair(y , z));
}
bool dfs(int now , double k){ //判环还是dfs_spfa好用
vis[now] = 1;
for(int i = 0; i < e[now].size(); i++){
int nx = e[now][i].first;
double w = e[now][i].second;
if(dis[nx] < dis[now] + w - k){ //注意这里减k,因为如果减去平均值(答案)还是正环的话,那说明这个环是符合条件的
//如果减去平均值之后变成负环后,说明这个平均值太大了,要重新二分k
dis[nx] = dis[now] + w - k;
if(vis[nx]) return 1; //如果找到环,返回找到
else if(dfs(nx , k)) return 1; //继续寻找是不是环
}
}
vis[now] = 0;
return 0;
}
bool cheak(double k){
memset(vis , 0 , sizeof(vis));
memset(dis , 0 , sizeof(dis));
for(int i = 1; i <= 3000; i++) //枚举每一个节点,图有可能不是联通的
if(dfs(i , k)) return 1;
return 0;
}
int main(){
while(1){
cin >> n;
if(!n) break;
for(int i = 1; i <= 3000; i++) e[i].clear(); //清空一下图
for(int i = 1; i <= n; i++){
char a[3010];
cin >> a + 1; //从一开始输入
int len = strlen(a + 1);
add(id(a[1] , a[2]) , id(a[len - 1] , a[len]) , len); //建边
}
l = 0 , r = 3000; //r别取太大了,不然会T,也别太小了,说不定正确答案都比r大
while(r - l > eps){
mid = (l + r) / 2.0;
if(cheak(mid)) l = mid;
else r = mid;
}
if(l == 0) cout << "No solution" << endl; //使平均值为0都没有环,果断无解
else printf("%.2f
" , l);
}
return 0;
}