【最大环】
1504: 【例 1】Word Rings
将一个字符串看成一条边,字符两端的字符看成节点,长度看成权值。二分枚举答案,最后SPFA刷正环,因为只要有一个正环存在就可以了。
把每个单词视为一条边,由开头两字母指向结尾两字母,边权为单词长度。则原问题转化为求一个最优比率环。(和最优比率生成树很像哈)
可以利用二分答案+spfa判环来解决。点最多有26*26个,边最多1e5.dfs版spfa判环就是快。。
若不存在环串,输出 No solution,否则输出最长的环串的平均长度。
QUS:为什么不是单词之间连边?
这样做最坏情况下会有 105 个点,1010 条边,时间和空间都会承受不了
换一种建图的方式:
对于每一个单词,将它的前两个字符和后两个字符之间连一条长度为当前单词的长度的边。
稍微思考一下就会发现这种建图方式其实是和第一种等价的。
建完图后,这个问题就变成了一个 01 分数规划问题,要求的是 ∑单词长度总和/∑单词个数 的最大值。
然后就是一些基本操作:二分答案 + SPFA 找 正环。
注意 SPFA 时需要加一些优化:
记录一下进行过松弛操作的点的数量,如果所有的点都已经做过了,就说明很大可能存在一个正环,直接返回 true;【或者直接vis过也可以】
为什么二分+SPFA判正环
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=1e5+10; const int INF=0x3fffffff; const double eps=1e-4; typedef long long LL; //将一个字符串看成一条边,字符两端的字符看成节点,长度看成权值。二分枚举答案,最后SPFA刷正环,因为只要有一个正环存在就可以了。 //把每个单词视为一条边,由开头两字母指向结尾两字母,边权为单词长度。则原问题转化为求一个最优比率环, //可以利用二分答案+spfa判环来解决。点最多有26*26个,边最多1e5.dfs版spfa判环就是快。。 //这道题还是不太理解 //我知道了,我这个题都不读的女人 //若不存在环串,输出 No solution,否则输出最长的环串的平均长度。 //这个是求最大环 char aa[1010]; int head[maxn],vis[maxn]; double dis[maxn]; int n,cnt; struct node{ int to,next,dis; }ed[maxn*10]; void add(int a,int b,int c){ ed[++cnt].to=b; ed[cnt].dis=c; ed[cnt].next=head[a]; head[a]=cnt; } int js(char a,char b){ return (a-'a')*26+(b-'a')+1; //要加1 } bool spfa(int x,double mid){ vis[x]=1; for(int i=head[x];i;i=ed[i].next){ int t=ed[i].to; if(dis[x]+ed[i].dis-mid>dis[t]){ //!!!!因为是想最大嘛 dis[t]=dis[x]+ed[i].dis-mid; if(vis[t]||spfa(t,mid)) { //如果t已经访问过或者后面能够返回1,那么就存在环,就可以返回1 vis[x]=0; return 1; } } } vis[x]=0; return 0; } bool judge(double mid){ memset(dis,0,sizeof(dis)); for(int i=1;i<=26*26;i++){ //枚举!!!!!!点最多有26*26个,枚举以每一个点为起点 if(spfa(i,mid)) return 1; } return 0; } int main(){ while(scanf("%d",&n)&&n){ memset(vis,0,sizeof(vis)); memset(head,0,sizeof(head)); for(int i=0;i<n;i++){ scanf("%s",aa); int len=strlen(aa); add(js(aa[0],aa[1]),js(aa[len-2],aa[len-1]),len); //建边 } double mid,left=0,right=1000; //二分 while(right-left>=eps){ mid=(left+right)/2; if(judge(mid)){ left=mid; } else right=mid; } if(left==0) printf("No solution\n"); else printf("%.2lf\n",left); } return 0; }
【最小环】
1506:最小圈
很明显是个分数规划的题目 如果我们能够在图中找到一个负环,就说明ans可以继续缩小 使用实数二分即可
跟word rings比较像,但是这个是求最小
但是注意一点:word rings: if(judge(mid)) left=mid
这道题:if(judge(mid)) right=mid
#include<iostream> #include<cstring> #include<cmath> #include<algorithm> #include<stack> #include<cstdio> #include<queue> #include<map> #include<vector> #include<set> using namespace std; const int maxn=3010; const int maxm=1e4+10; const int INF=0x3fffffff; const int eps=1e-9; typedef long long LL; //很明显是个分数规划的题目 如果我们能够在图中找到一个负环,就说明ans可以继续缩小 使用实数二分即可 int n,m; int head[maxn]; struct node{ int to,next; double dis; }ed[maxm]; int vis[maxn],cnt; double dis[maxn]; void adde(int a,int b,double c){ cnt++; ed[cnt].to=b; ed[cnt].next=head[a]; ed[cnt].dis=c; head[a]=cnt; } bool dfs(int now,double mid){ vis[now]=1; for(int i=head[now];i!=0;i=ed[i].next){ int t=ed[i].to; if(dis[t]>dis[now]+ed[i].dis-mid){ dis[t]=dis[now]+ed[i].dis-mid; if(vis[t]) return 1; else if(dfs(t,mid)) return 1; } } vis[now]=0; return 0; } bool ju(double mid){ memset(vis,0,sizeof(vis)); memset(dis,0,sizeof(dis)); for(int i=1;i<=n;i++){ if(dfs(i,mid)) return 1; } return 0; } int main(){ scanf("%d %d",&n,&m); int x,y; double z; double l=1e8,r=-1e8,mid; for(int i=0;i<m;i++){ scanf("%d %d %lf",&x,&y,&z); adde(x,y,z); //有向图 l=min(l,z); r=max(r,z); } while(r-l>eps){ mid=(l+r)/2.0; if(ju(mid)) r=mid; else l=mid; } printf("%.8lf\n",l); return 0; }