负环
若图中存在一个负环,环上各边的权值之和是负数,则称这个环为负环。
我们常用第二种判负环方法,即
优化
- 若使用普通的判负环出现了超时,则使用写题经验,
表示所有点一共被更新的次数,若
或
大于一个比较大的数 ,则可以判断已经出现负环。(详见 单词环 代码)
- 若使用普通的判负环出现了超时,则使用写题经验,我们将存储数据的 队列 改成 栈,可能会大大降低运行时间。只有出现普通判负环超时时再用,否则使用栈可能会超级慢。
总结
- 对于判负环问题,我们需要一个 超级源点,保证图中的每一条边都可以遍历到,因此我们建立一个超级源点,让超级源点与
个点分别连一条权值为
的边。实际上,我们在代码实现时可以省去超级源点与
个点连边的这一步,我们直接将这
个点加入到队列当中。
for(int i=1;i<=n;i++){
q[tt++]=i;
vis[i]=true;
}
例题:
1. AcWing 904. 虫洞
该题为模板题
若判断该图中是否有负环,则我们需要一个 超级源点,保证图中的每一条边都可以遍历到,因此我们建立一个超级源点,让超级源点与 个点分别连一条权值为
的边。实际上,我们在代码实现时可以省去超级源点与
个点连边的这一步,我们直接将这
个点加入到队列当中。
#include<bits/stdc++.h>
using namespace std;
const int N=510,M=5500;
struct node{
int nex,to,w;
}e[M];
int head[N],cnt,n,m1,m2;
int vis[N],q[N],tot[N],dis[N];
void add(int u,int v,int w){
e[++cnt].nex=head[u];
e[cnt].to=v;
e[cnt].w=w;
head[u]=cnt;
}
bool spfa(){
memset(vis,0,sizeof(vis));
memset(dis,0,sizeof(dis));
memset(tot,0,sizeof(tot));
int hh=0,tt=0;
for(int i=1;i<=n;i++){
q[tt++]=i;
vis[i]=1;
}
while(hh!=tt){
int u=q[hh++];
if(hh==N) hh=0;
vis[u]=0;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].to,w=e[i].w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
tot[v]=tot[u]+1;
if(tot[v]>=n) return true;
if(!vis[v]){
q[tt++]=v;
if(tt==N) tt=0;
vis[v]=1;
}
}
}
}
return false;
}
int main(){
int T;
scanf("%d",&T);
while(T--){
memset(head,0,sizeof(head));
cnt=0;
scanf("%d%d%d",&n,&m1,&m2);
for(int i=1;i<=m1;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w); add(v,u,w);
}
for(int i=1;i<=m2;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,-w);
}
if(spfa()) printf("YES\n");
else printf("NO\n");
}
return 0;
}
分数规划
分数规划 问题:
给你一张图,图上每个点都有一个权值
,每一条边都有一个权值
,图保证有一个环。求出图中的一个环,使得“环上各点的权值之和”除以“环上各边的权值之和” 最大。
即使得环
的值最大。
对于此类问题,我们使用二分做法,使得
。
我们将该式子化简
图中是否存在正环
我们发现将点上的权值
放到边上并不会影响答案,因此将
放到边上,计算
值时用到
。
对于此题我们将
我们二分
的答案,若当前的
合法,即存在正环,则
,否则
,最终的
即为答案
while(r-l>1e-4){ double mid=(l+r)/2; if(check(mid)) l=mid; else r=mid; }
例题:
1. AcWing 361. 观光奶牛
此题就是刚刚上述讲的如何解决 分数规划问题的题,我们直接按照上述讲的做即可,不再赘述。
#include<bits/stdc++.h>
using namespace std;
const int N=1010,M=5010;
struct node{
int nex,to,w;
}e[M];
int head[N],cnt,tot[N],q[N];
double dis[N];
int n,m,f[N];
bool vis[N];
void add(int u,int v,int w){
e[++cnt].nex=head[u];
e[cnt].to=v;
e[cnt].w=w;
head[u]=cnt;
}
int check(double mid){
int hh=0,tt=0;
memset(vis,0,sizeof(vis));
memset(tot,0,sizeof(tot));
for(int i=1;i<=n;i++){
q[tt++]=i;
vis[i]=true;
}
while(hh!=tt){
int u=q[hh++];
if(hh==N) hh=0;
vis[u]=false;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].to;
double w=f[u]-mid*e[i].w;
if(dis[v]<dis[u]+w){
dis[v]=dis[u]+w;
tot[v]=tot[u]+1;
if(tot[v]>=n) return true;
if(!vis[v]){
q[tt++]=v;
if(tt==N) tt=0;
vis[v]=true;
}
}
}
}
return false;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&f[i]);
for(int i=1;i<=m;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
}
double l=0,r=1000;
while(r-l>1e-4){
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%.2lf\n",l);
return 0;
}
2. AcWing 1165. 单词环
要求的答案为 ,求环的最大的
,其中
为这个环由几个单词构成。
我们发现这道题只用到了每一个单词的首2个字母、 尾2个字母和该单词的长度。
因此两个字母的情况只有 种。
因此对于每一个单词,其首2个字母为 ,尾2个字母尾
,该单词的长度为
,则
此题的平均长度不能为 ,因此特判一下
,如果非真,则输出
No solution
此题用了一个小优化,若使用普通的判负环出现了超时,则使用写题经验, 表示所有点一共被更新的次数,若
或
大于一个比较大的数 ,则可以判断已经出现负环。由于此题
太小了,所有写了个较大的数。
#include<bits/stdc++.h>
using namespace std;
const int N=700,M=100010;
struct node{
int nex,to,w;
}e[M];
int head[N],cnt,vis[N],tot[N],q[N];
double dis[N];
int n;
char s[1010];
void add(int u,int v,int w){
e[++cnt].nex=head[u];
e[cnt].to=v;
e[cnt].w=w;
head[u]=cnt;
}
bool check(double mid){
memset(tot,0,sizeof(tot));
memset(vis,0,sizeof(vis));
int hh=0,tt=0;
for(int i=0;i<676;i++){
q[tt++]=i;
vis[i]=1;
}
int count=0;
while(hh!=tt){
int u=q[hh++];
if(hh==N) hh=0;
vis[u]=0;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].to,w=e[i].w;
if(dis[v]<dis[u]+w-mid){
dis[v]=dis[u]+w-mid;
tot[v]=tot[u]+1;
if(++count>10000) return true;
if(tot[v]>=N) return true;
if(!vis[v]){
q[tt++]=v;
if(tt==N) tt=0;
vis[v]=1;
}
}
}
}
return false;
}
int main(){
while(scanf("%d",&n)&&n){
memset(head,0,sizeof(head));
cnt=0;
for(int i=1;i<=n;i++){
getchar();
scanf("%s",s);
int len=strlen(s);
if(len>=2){
int u=(s[0]-'a')*26+s[1]-'a';
int v=(s[len-2]-'a')*26+s[len-1]-'a';
add(u,v,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;
}