洛谷 SP2885 【WORDRING

最近做了好多二分+最短路的题啊


看到环之后就能想到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;
}
原文地址:https://www.cnblogs.com/bzzs/p/13235300.html