Description
给出一张有向图,求一个回路使其点权和/边权和最大(保证有回路)
Input
第一行为两个整数n和m表示点数和边数,之后n个整数表示n个点的点权,最后m行每行三个整数a,b,c表示a点到b点有一条权值为c的边
Output
输出一条回路的最大点权和/边权和值
Sample Input
5 7
30
10
10
5
10
1 2 3
2 3 2
3 4 5
3 5 2
4 5 5
5 1 3
5 2 2
Sample Output
6.00
Solution
01分数规划,即求最大的ans=sum(val[i])/sum(cost[u][v]),方程可化为ans*sum(cost[u][v])-sum(val[i])=0,二分ans,每次将u->v的边权变为ans*cost[u][v]-val[u],判断图中是否存在负环,如果存在说明ans过小,不存在说明ans过大,判负环可以用spfa来判,即若一个点入队列数超过点数n说明存在负环
Code
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
#define INF 0x3f3f3f3f
#define maxn 1111
#define maxm 11111
#define eps 1e-3
struct edge
{
int to,next;
double cost;
}g[maxm];
int head[maxm],tol,cnt[maxn];
int n,m;
double val[maxn],dis[maxn];
void init()
{
memset(head,-1,sizeof(head));
tol=0;
}
void add(int u,int v,double c)
{
g[tol].cost=c;
g[tol].to=v;
g[tol].next=head[u];
head[u]=tol++;
}
int spfa(double x)
{
bool vis[maxn];
memset(vis,false,sizeof(vis));
memset(cnt,0,sizeof(cnt));
queue<int>que;
while(!que.empty())que.pop();
for(int i=0;i<=n;i++)
dis[i]=INF;
cnt[1]++;
dis[1]=0;
vis[1]=true;
que.push(1);
while(!que.empty())
{
int u=que.front();
que.pop();
vis[u]=false;
for(int i=head[u];i!=-1;i=g[i].next)
{
int v=g[i].to;
double c=g[i].cost;
double w=x*c-val[u];
if(dis[v]>dis[u]+w)
{
dis[v]=dis[u]+w;
if(!vis[v])
{
vis[v]=true;
que.push(v);
cnt[v]++;
if(cnt[v]>=n)return 1;
}
}
}
}
return 0;
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
init();
for(int i=1;i<=n;i++)scanf("%lf",&val[i]);
for(int i=1;i<=m;i++)
{
int u,v;double c;
scanf("%d%d%lf",&u,&v,&c);
add(u,v,c);
}
double l=0,r=1111,mid;
while(fabs(r-l)>eps)
{
mid=(l+r)/2;
if(spfa(mid))l=mid;
else r=mid;
}
printf("%.2lf\n",mid);
}
return 0;
}