题目大意:
题目链接:https://www.luogu.org/problemnew/show/P1768
一个有向图,每条边有权值
v
[
i
]
,
p
[
i
]
v[i],p[i]
v[i],p[i],你需要找到一个环,使得
∑
v
[
i
]
∑
p
[
i
]
\frac{\sum v[i]}{\sum p[i]}
∑p[i]∑v[i]尽量大。
思路:
看到
∑
v
[
i
]
∑
p
[
i
]
\frac{\sum v[i]}{\sum p[i]}
∑p[i]∑v[i],很容易想到是一道01分数规划问题。
我们设
a
n
s
=
m
a
x
{
∑
v
[
i
]
∑
p
[
i
]
}
ans=max\{\frac{\sum v[i]}{\sum p[i]}\}
ans=max{∑p[i]∑v[i]},那么对于任意的
∑
v
[
i
]
∑
p
[
i
]
\frac{\sum v[i]}{\sum p[i]}
∑p[i]∑v[i],都有
∑
v
[
i
]
∑
p
[
i
]
≤
a
n
s
\frac{\sum v[i]}{\sum p[i]}\leq ans
∑p[i]∑v[i]≤ans
移项得
∑
v
[
i
]
≤
a
n
s
×
∑
p
[
i
]
\sum v[i]\leq ans\times \sum p[i]
∑v[i]≤ans×∑p[i]
再次移项得
∑
(
p
[
i
]
×
a
n
s
)
−
∑
v
[
i
]
≥
0
\sum(p[i]\times ans)-\sum v[i]\geq 0
∑(p[i]×ans)−∑v[i]≥0
也就是说
∑
(
p
[
i
]
×
a
n
s
−
v
[
i
]
)
≥
0
\sum(p[i]\times ans-v[i])\geq 0
∑(p[i]×ans−v[i])≥0
于是我们可以把所有的边的边权更改为
p
[
i
]
×
a
n
s
−
v
[
i
]
p[i]\times ans-v[i]
p[i]×ans−v[i],只要现在图中有任意一个环得边权和为负数,那么
a
n
s
ans
ans就是不符合要求的。
那就二分
a
n
s
ans
ans。题目中说了保证答案不超过200,于是时间复杂度就是
O
(
n
m
l
o
g
(
200
)
)
O(nm\ log(200))
O(nm log(200))。
温馨提示:
- 这道题卡 b f s bfs bfs的 s p f a spfa spfa。请使用 d f s dfs dfs版本的 s p f a spfa spfa。
- 图是不一定连通的。所以可以建立一个超级源点0,连向所有的边。 s p f a spfa spfa直接从0开始跑。
代码:
#include <cstdio>
#include <queue>
#include <cstring>
using namespace std;
const int N=7010;
const int M=30010;
int n,m,x,y,v,p,tot,head[N];
double l,r,mid,dis[N];
bool vis[N];
struct edge
{
int to,next;
double v,p,dis;
}e[M];
void add(int from,int to,int v,int p)
{
e[++tot].to=to;
e[tot].v=(double)v;
e[tot].p=(double)p;
e[tot].next=head[from];
head[from]=tot;
}
bool spfa(int x) //dfs版spfa1求负环
{
vis[x]=1;
for (int i=head[x];~i;i=e[i].next)
{
int y=e[i].to;
if (dis[y]>dis[x]+e[i].dis)
{
if (vis[y]) return 0;
//dfs版不用cnt数组,如果访问到一个点时,这个点还在栈里,说明有负环
dis[y]=dis[x]+e[i].dis;
vis[y]=1;
if (!spfa(y)) return 0;
}
}
vis[x]=0;
return 1;
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++)
{
scanf("%d%d%d%d",&x,&y,&v,&p);
add(x,y,v,p);
}
for (int i=1;i<=n;i++)
add(0,i,0,0);
l=0;
r=200;
while (r-l>0.01)
{
mid=(l+r)/2;
for (int i=1;i<=m;i++)
e[i].dis=e[i].p*mid-e[i].v;
for (int i=0;i<=n;i++)
dis[i]=1e9,vis[i]=0;
dis[0]=0;
if (spfa(0)) r=mid;
else l=mid;
}
if (l==0) printf("-1");
else printf("%0.1lf",l);
return 0;
}