[USACO07DEC] Sightseeing Cows G(分数规划+负权回路判定)

题面

[USACO07DEC] Sightseeing Cows G - 洛谷

题目大意:

给出一张n点m边的带点权带边权的有向图

求一个回路使得路上点权和除以边权和最大(最优比率回路)

题解

首先一定仔细读题,是回路不是路径

由于回路上所有点权只能获取一次,但边权会获取很多次,所以最优解一定是简单回路(无重复边)

然后我们发现是让一个分数最大,于是我们可以考虑分数规划二分

假设二分的商为mid,判断是否存在一个满足点边权和比大于mid的回路,则二分判断条件为

\sum_{u\in V_p}valv[u]/\sum_{e\in E_p}vale[e]> mid(Vp为最优简单回路的点集,Ep为最优简单回路的边集)

稍微变换一下形式

\sum_{u\in V_p}valv[u]-mid*\sum_{e\in E_p}vale[e]> 0

这个判断条件依旧不好计算,因为我们二分的目的就是为了把最优化问题转换为判断性问题,但是对于该条件,我们除了枚举所有的简单回路,没有任何切入点

转念一想,最后这个大于0似乎暗藏玄机,又和回路联系在一起

不由得让人想到判断图是否存在正权回路(可以直接取相反数转化为判断负权回路)

为了把问题往这个方向转,我们尝试把点权下放到边权

\sum_{e=<u,v>\in E_p}-(valv[v]-mid*vale[e])< 0

把每条边边权设置为 -(它要通往的点的点权-mid*它原本的边权)

但是这样又会有一个新的问题,如果我们最后选出来的回路是这个样子

这样不就会重复计算点权了吗?

事实上这种情况是不存在的

我们可以感性的证明一下:

出现重复点的简单回路,一定可以拆解成若干初级回路(无重复点、无重复边)

整个回路最终的比率一定不会超过其中比率最大的初级回路((a+b)/(c+d)<=max(a/c,b/d),似乎在小学奥数里面叫糖水不等式?)(而且整个回路的比率的分子还会减去重复点的点权)

所以,我们选择一定是选择简单回路里面最优的初级回路

至此,我们就可以下放点权了

愉快地使用SPFA进行负权回路判断

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
#define N 1005
#define M 10005
#define INF 1000000000
int n,m;
int F[N];
struct enode{
	int u,v,w;
}E[M];
int fir[N],to[M],nxt[M],cnt;
double cd[M];
void adde(int a,int b,double c)
{
	to[++cnt]=b;cd[cnt]=c;nxt[cnt]=fir[a];fir[a]=cnt;
}
double dis[N];
int con[N];
bool inq[N];
queue<int> Q;
bool check()
{
	int i;
	for(i=1;i<=n;i++)dis[i]=INF;
	for(i=1;i<=n;i++)con[i]=0;
	for(i=1;i<=n;i++)inq[i]=0;
	for(i=1;i<=n;i++){
		if(dis[i]==INF){
			dis[i]=0;inq[i]=1;
			Q.push(i);
			while(!Q.empty()){
				int u=Q.front();Q.pop();inq[u]=0;
				for(int p=fir[u];p;p=nxt[p]){
					int v=to[p];
					double w=cd[p];
					if(dis[u]+w<dis[v]){
						dis[v]=dis[u]+w;
						con[v]++;
						if(!inq[v]){Q.push(v);inq[v]=1;}
						if(con[v]>=n)return 1;
					}
				}
			}
		}
	}
	return 0;
}
int main()
{
	int i;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)
		scanf("%d",&F[i]);
	for(i=1;i<=m;i++)
		scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].w);
	double l=0,r=1000,mid;
	while(r-l>1e-3){
		mid=(l+r)/2;
		cnt=0;
		for(i=1;i<=n;i++)fir[i]=0;
		for(i=1;i<=m;i++)
			adde(E[i].u,E[i].v,-(1.0*F[E[i].v]-mid*E[i].w));
		if(check())
			l=mid;
		else
			r=mid;
	}
	printf("%.2f",l);
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是P4087 [USACO17DEC]Milk Measurement的c++代码: ```c++ #include<bits/stdc++.h> using namespace std; int n,d,i,x,minn=1e9,maxn=-1e9,sum=7;//注意sum要初始化为7,因为一开始有三个人挤奶! map<int,int> mp; struct node{ int day,milk,id;//day表示某一天,milk表示这一天的产奶量,id表示这头牛的编号 }a[100010]; bool cmp(node x,node y){ return x.day<y.day; } int main(){ scanf("%d%d",&n,&d); for(i=1;i<=n;i++){ scanf("%d%d%d",&a[i].day,&a[i].id,&a[i].milk); minn=min(minn,a[i].id);//记录最小的牛的编号 maxn=max(maxn,a[i].id);//记录最大的牛的编号 } sort(a+1,a+n+1,cmp);//排序 for(i=1;i<=n;i++){ int p=a[i].id; mp[p]+=a[i].milk;//记录每头牛产奶总量 if(mp[p]-a[i].milk>=mp[minn]&&mp[p]>=mp[minn]){//如果这头牛的产奶总量减去这一天的产奶量后等于最小产奶量且这头牛的产奶总量大于等于最小产奶量 sum--; } if(mp[p]>=mp[maxn]&&mp[p]-a[i].milk<mp[maxn]){//如果这头牛的产奶总量大于等于最大产奶量且这头牛的产奶总量减去这一天的产奶量小于最大产奶量 sum++; } if(mp[p]-a[i].milk<mp[maxn]&&mp[p]>=mp[maxn]){//如果这头牛的产奶总量减去这一天的产奶量小于最大产奶量且这头牛的产奶总量大于等于最大产奶量 if(mp[maxn]-mp[p]+a[i].milk>0)sum++; } mp[p]-=a[i].milk;//减去这一天的产奶量 if(i==n||a[i].day!=a[i+1].day){//如果到了新的一天或者到了最后一天 if(mp[maxn]!=mp[a[i].id]&&mp[a[i].id]>=mp[maxn])sum++;//如果这头牛的产奶总量不等于最大产奶量且这头牛的产奶总量大于等于最大产奶量 if(mp[maxn]==mp[a[i].id]){//如果这头牛的产奶总量等于最大产奶量 if(a[i].id==maxn)sum+=0;//如果这头牛就是最大产奶量的牛,那么不需要增加计数器 else sum++;//否则需要增加计数器 } if(mp[minn]!=mp[a[i].id]&&mp[a[i].id]>=mp[minn])sum++;//如果这头牛的产奶总量不等于最小产奶量且这头牛的产奶总量大于等于最小产奶量 if(mp[minn]==mp[a[i].id]){ if(a[i].id==minn)sum+=0;//如果这头牛就是最小产奶量的牛,那么不需要增加计数器 else sum++;//否则需要增加计数器 } } } printf("%d\n",sum); return 0; } ``` 该题的解题思路是模拟,需要注意细节问题。我们可以首先将输入的数据按天数排序,然后模拟每一天挤奶的情况,并根据题目要求进行计数即可。具体细节请见代码注释。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值