【洛谷P1768】天路【负环】【二分】【数论】
题目大意:
题目链接:https://www.luogu.org/problemnew/show/P1768
一个有向图,每条边有权值,你需要找到一个环,使得尽量大。
思路:
看到,很容易想到是一道01分数规划问题。
我们设,那么对于任意的,都有
移项得
再次移项得
也就是说
于是我们可以把所有的边的边权更改为,只要现在图中有任意一个环得边权和为负数,那么就是不符合要求的。
那就二分。题目中说了保证答案不超过200,于是时间复杂度就是。
温馨提示:
- 这道题卡的。请使用版本的。
- 图是不一定连通的。所以可以建立一个超级源点0,连向所有的边。直接从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;
}