图论(01分数规划+负环spfa)P2868 [USACO07DEC]

题目描述

给你一张 \(n\)\(m\) 边的有向图,第 \(i\) 个点点权\(F_{i}\),第 \(i\) 条边边权\(T_{i}\)

找一个环,设环上的点组成的集合为 \(S\),环的边组成的集合为 \(E\),最大化\( \frac{\sum_{u \in S}F_{u}} {{\sum_{e \in E} T_{e}}}\)的值

思路分析

先给一个显而易见的小结论:任意一个环中的点数和边数一定相等 我们假设有\(m\)

接着假设最大答案为\(ans\)

\[ans\geq \frac{\sum_{i}^m F_{i}}{\sum_{i}^m T_{i}} \]

移项,

\[ans \times {\sum_{i}^m T_{i}} \geq {\sum_{i}^m F_{i}} \]

运用乘法分配律,

\[{\sum_{i}^m (T_{i}\times ans)} \geq {\sum_{i}^m F_{i}} \]

移项,使用加(减)法交换结合律,

\[{\sum_{i}^m (T_{i}\times ans-F_{i}) }\geq 0 \]

得到结论:答案 \(ans\)\({\sum_{i}^m (T_{i}\times ans-F_{i}) }\geq 0\)条件下合法 求解最大的\(ans\)

我们把\({\sum_{i}^m (T_{i}\times ans-F_{i}) }\)设为\(f(ans)\) 得到\(f(ans)\)为增函数

尝试二分\(ans\) \((ans \in [10^{-3},10^3])\)

  • case 1:\(f(ans)<0\) 不满足\(ans\)的条件 应增大\(ans\)的值 (代码l=mid)
  • case2:\(f(ans)\geq 0\)满足\(ans\)的条件 应减小\(ans\)的值(代码r=mid)

由此可以打出\(01\)分数规划二分的板子:

    double l=0.001,r=1000;
	double mid;
    while(r-l>0.0001)
    {
        mid=(l+r)/2.0;
        if(check(mid)<0) l=mid;
        else r=mid;
    }

想一想怎么打\(check()\)函数

考虑\(f(ans)<0\)\({\sum_{i}^m (T_{i}\times ans-F_{i}) }< 0\)

因为要求选的点形成一个环 而\({\sum_{i}^m (T_{i}\times ans-F_{i}) }\)值又为负数 可以很显然想到负环算法

我们给每条边重新赋值边权 对于任意边\([u,v]\)边权赋值为\(w[u][v]\times ans-F_u\)

那么判断负环的公式就是\({\sum_{i}^m (T_{i}\times ans-F_{i}) }< 0\)\(f(ans)<0\)

这样就实现了用负环算法完成\(check\)函数

代码实现

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int N=5e3+10;
int f[N];
struct edge
{
    int v,w;
};
vector<edge> e[N];
int num[N];
double dis[N];
bool vis[N];
int n,m;
bool spfa(double ans) 
{
	queue<int> q;
	memset(num,0,sizeof(num));
	memset(vis,0,sizeof(vis));
	memset(dis,0,sizeof(dis));
	for(int i=1;i<=n;++i) 
    {
		q.push(i);
		vis[i]=1;
	}
	while(!q.empty()) 
    {
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=0;i<e[u].size();++i)
         {
			int v=e[u][i].v;
			double w=e[u][i].w;
            
            w=w*ans-f[u];//重新赋值边权

			if(dis[v]>dis[u]+w)
            {
				dis[v]=dis[u]+w;
				num[v]=num[u]+1;

				if(num[v]>=n) return 1;
				if(!vis[v]) 
                {
					q.push(v);
					vis[v]=1;
				}
			}
		}
	}
	return 0;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);

    cin>>n>>m;

    for(int i=1;i<=n;++i)
        cin>>f[i];
    
    for(int i=1;i<=m;++i)
    {
        int u,v,w;
        cin>>u>>v>>w;
        e[u].push_back({v,w});
    }

    double l=0.001,r=1000;
	double mid;
    while(r-l>0.0001)
    {
        mid=(l+r)/2.0;
        if(spfa(mid)) l=mid;
        else r=mid;
    }
    
    printf("%.2lf",mid);
    return 0;
}

posted @ 2025-03-01 18:02  SamXia  阅读(5)  评论(0)    收藏  举报