POJ 3621 Sightseeing Cows(最優比率環+spfa的dfs判環優化)
技術標籤:圖論dfs剪枝數學建模bellman–ford algorithm圖論
題目連結
題目大意:給你一個有向圖,每個點都有一個權值,每條邊都有一個長度,需要找到一個各點權值和/各邊長度和 最大的環出來,輸出這個比值。
分析:假設結點權值為
w
i
w_i
wi,邊的長度為
l
i
l_i
li設所求的比值
r
=
∑
i
=
1
m
w
v
i
∗
x
i
∑
i
=
1
m
l
i
∗
x
i
(
x
i
=
0
或
1
,
1
<
i
<
m
)
r=\frac{\sum_{i=1}^{m}w_{vi}*x_i}{\sum_{i=1}^ml_i*x_i}(x_i=0或1,1<i<m)
則滿足以下式子
a
n
s
>
=
∑
i
=
1
m
w
v
i
∗
x
i
∑
i
=
1
m
l
i
∗
x
i
ans>=\frac{\sum_{i=1}^{m}w_{vi}*x_i}{\sum_{i=1}^ml_i*x_i}
ans>=∑i=1mli∗xi∑i=1mwvi∗xi
移項可得
∑
i
=
1
m
w
v
i
∗
x
i
−
a
n
s
∗
∑
i
=
1
m
l
i
∗
x
i
<
=
0
\sum_{i=1}^{m}w_{vi}*x_i-ans*\sum_{i=1}^ml_i*x_i<=0
我們設
f
(
a
n
s
)
=
∑
i
=
1
m
w
v
i
∗
x
i
−
a
n
s
∗
∑
i
=
1
m
l
i
∗
x
i
f(ans)=\sum_{i=1}^{m}w_{vi}*x_i-ans*\sum_{i=1}^ml_i*x_i
f(ans)=∑i=1mwvi∗xi−ans∗∑i=1mli∗xi
f
(
a
n
s
)
=
∑
i
=
1
m
d
i
∗
x
i
,
(
d
i
=
w
v
i
−
a
n
s
∗
l
i
)
f(ans)=\sum_{i=1}^md_i*x_i,(d_i=w_{vi}-ans*l_i)
這個關於ans的函式是隨ans變大而減小的,當f<=0時滿足條件,當且僅當f=0時ans取得最大值,則我們就可以把每條邊的權值設為
d
i
d_i
di,然後二分ans,當用當前ans算出的f>0時則表明ans過小且不合法,當f<0時說明ans過大,而題目中要求的是一個環,當f<=0時是滿足題意的,但我們不好判斷,所以我們選擇去判斷f>0即圖中存在正環。
判正環的演算法可以用樸素的spfa也可dfs優化,最終的時間是樸素的是900+ms,而dfs優化則是30+ms
下面是dfs優化的
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#define MAIN main
#define PII pair<int,int>
#define x first
#define y second
using namespace std;
typedef long long ll;
const double eps=1e-8;
const double pi=acos(-1.0);
const int inf=0x3f3f3f3f;
const int N=1e3+10,M=5e3+10;
struct edge
{
int v,next,w;
double d;
}e[M<<1];
int head[N],cnt;
void add(int u,int v,int w)
{
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].next=head[u];
head[u]=cnt;
}
int w[N],n,m,vis[N],ok;
double dis[N];
void dfs(int u)
{
if(ok) return;
vis[u]=1;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].v;
if(dis[v]<dis[u]+e[i].d)
{
dis[v]=dis[u]+e[i].d;
if(vis[v])
{
ok=1;
return;
}
dfs(v);
}
}
vis[u]=0;
}
bool judge(double mid)
{
for(int i=1;i<=cnt;i++) e[i].d=w[e[i].v]*1.0-mid*(e[i].w*1.0);
for(int i=1;i<=n;i++) vis[i]=0,dis[i]=0;
ok=0;
for(int i=1;i<=n;i++){
if(!vis[i]) dfs(i);
if(ok) return true;
}
return false;
}
int MAIN()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
for(int i=1;i<=m;i++){
int u,v,l;
scanf("%d%d%d",&u,&v,&l);
add(u,v,l);
}
double l=0,r=1e6,mid,ans;
while(r-l>0.0001)
{
mid=(l+r)/2;
if(judge(mid)) l=mid;
else ans=mid,r=mid;
}
printf("%.2f\n",ans);
return 0;
}