0|1分数规划

这种分子上一大坨求和,分母上一大坨求和,然后求最值的问题属于一类特殊的问题。被称为0|1分数规划问题。
也就是\(\frac{\sum{f_i} * w_i} {\sum{g_i} *w_i }\)
找一组\({w_i} \in \{0,1\}\)使上面的式子最小
通常的做法就是二分答案
假设我们要求的是最大值,然后二分一个答案mid,然后推式子

\[\frac{\sum{f_i} * w_i} {\sum{g_i} *w_i }>mid \]

\[{\sum{f_i} * w_i}-mid*{\sum{g_i} *w_i }>0 \]

\[w_i *({\sum{f_i}-mid*g_i})>0 \]

那么只要求出不等号左边的式子的最大值就行了。如果最大值比 0 要大,说明 mid 是可行的,否则不可行

针对于这道题目也是
这里既有边权又有点权,可以把点权下放到出边上
然后上面的式子的含义也就变为了在图上求正环。
正环也可以用spfa来求,只需要和负环对称一下,求最短路变成求最长路即可。

#include <bits/stdc++.h> 
#define int long long
#define rep(i,a,b) for(int i = (a); i <= (b); ++i)
#define fep(i,a,b) for(int i = (a); i >= (b); --i)
#define pii pair<int, int>
#define ll long long
#define db double
#define endl '\n'
#define x first
#define y second
#define pb push_back
#define inf 0x3f3f3f3f*1ll

using namespace std;

void solve()
{
	int n,m;
	cin>>n>>m;
	vector<vector<pii>>g(n+1);
	vector<int>a(n+1);
	rep(i,1,n){
		cin>>a[i];
	}
	rep(i,1,m){
		int u,v,w;
		cin>>u>>v>>w;
		g[u].pb({v,w});
	}	
	auto check=[&](db tar){
		vector<int>inq(n+1,0);
		vector<int>cnt(n+1,0);
		vector<db>d(n+1,0);
		queue<int>q;
		rep(i,1,n){
			q.push(i);
			inq[i]=1;
		}
		while(q.size()){
			auto u=q.front();
			q.pop();
			inq[u]=0;
			for(auto it:g[u]){
				int v=it.x,w=it.y;
				if(d[v]<d[u]+a[u]-w*tar){
					d[v]=d[u]+a[u]-w*tar;
					cnt[v]=cnt[u]+1;
					if(cnt[v]>=n)	return true;
					if(!inq[v]){
						inq[v]=1;
						q.push(v);
					}
				}
			}
		}
		return false;
	};
	db l=0,r=1010;
	while(r-l>1e-5){
		db mid=(l+r)/2;
		if(check(mid))	l=mid;
		else	r=mid;
	}
	//c++默认输出6位精度
	cout<<setiosflags(ios::fixed)<<setprecision(2)<<l<<endl;
}

signed main(){
	ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
//   	freopen("1.in", "r", stdin);
	int _;
//	cin>>_;
//	while(_--)
	solve();
	return 0;
}

posted @ 2024-02-15 21:41  cxy8  阅读(15)  评论(0编辑  收藏  举报