SP2885 WORDRING - Word Rings
这道题是能够一眼看出思路的(然而我是错的),就是想求出所有的环,然后在所有环中比较出环串的平均长度最长的那一个,然后就完了
但是很明显这个东西基本无法实现(或者是我单纯太弱),因为无论我们想求平均长度最长,我们肯定希望跑最长路,刚好边权为字符串长度,那么跑进去正环之后就跑不出来了,所以这个想法换掉
那么继续想,我们无法枚举环来求出答案,我们就试着枚举答案,然后求出是否有满足答案的环就可以了
若我们此时枚举的答案为\(ans\),有\(k\)个字符串,那么就有
\(ans * k = len1 + len2 + len3 + ... + lenk\)
那道这个式子之后,我们对它进行移项
\(0=len1-ans+len2-ans+len3-ans+...+lenk-ans\)
那么对于满足以下式子,也可以判断是正环
\(0 \leq len1-ans+len2-ans+len3-ans+...+lenk-ans\)
那么对于这个\(ans\),\(ans\)越大,那么存在正环的概率也就越小,当\(ans\)越小时,\(ans\)大的情况肯定也会包含在里面,这就保证了单调性,可以使用二分答案来做
至于以上的最长路,应该可以使用Dijkstra判断正环,但是我还是喜欢用SPFA,因为可以判负环,而且大部分时候快一些(什么鬼才逻辑)
这里就直接上代码吧:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+51;
int n;
string s[MAXN];
struct node {
int net,to,w;
} e[MAXN];
int head[MAXN],tot;
void add(int x,int y,double z) {
e[++tot].net=head[x];
e[tot].to=y;
e[tot].w=z;
head[x]=tot;
}
int len[MAXN];
bool v[MAXN];
double d[MAXN];
bool dfs(int x,double k) {
v[x]=true;
for(register int i=head[x]; i; i=e[i].net) {
int y=e[i].to;
double z=e[i].w;
if(d[y]<d[x]+z-k) {
d[y]=d[x]+z-k; //不能理解这个式子的参考上面的解题思路
if(v[y]==true||dfs(y,k)==true) return true;
//如果当前点走过说明有正环
}
}
return v[x]=false; //记得回溯
}
int main() {
while(scanf("%d",&n)) {
if(n==0) return 0; //结束程序
memset(head,0,sizeof head);
tot=0; //记得清空
double l=0.0,r=1e8; //二分答案的范围,可以写大一点
for(register int i=1; i<=n; i++) {
cin>>s[i];
len[i]=s[i].length();
} //输入,记录长度,每次调用函数会慢一点
for(register int i=1; i<=n; i++) {
for(register int j=1; j<=n; j++) {
if(s[i][len[i]-1]==s[j][1]&&s[i][len[i]-2]==s[j][0]) {
add(i,j,len[i]); //枚举每两个字符串,如果可以相连就建边,边权就为字符串长度
}
}
}
bool ok=false; //记录是否有答案
while(r-l>0.001) { //实数域的二分,我的太丑啦~~
double mid=(l+r)/2;
bool op=0; //是否更新l和r
memset(v,false,sizeof v);
memset(d,0,sizeof d); //清空数组
for(register int i=1; i<=n; i++) {
if(dfs(i,mid)==true) { //如果有正环,说明有答案
ok=true;
op=1;
l=mid;
break;
}
}
if(!op)r=mid; //搜不出来说明r太大了
}
if(ok==false) puts("No solution");
else printf("%.2lf\n",l*1.0);
}
return 0;
}