acm-(好题、网络流思想、spfa、负环判定、分数规划)LuoGu P3288 [SCOI2014]方伯伯运椰子

题面
传送门
本题是一道分数规划题目。
建立新图,对于 u , v , a , b , c , d u,v,a,b,c,d u,v,a,b,c,d,考虑从 u u u v v v连一条权值为 b + d b+d b+d的边,代表扩大流量的额外花费,从 v v v u u u连一条权值为 a − d a-d ad的边,代表缩小流量的额外花费。注意到新图中的一个环恰好代表一组满足流量平衡的 扩 大 / 缩 小 扩大/缩小 /操作。为什么流量平衡呢,我们考虑原图中环上的每个节点的情况,情况一如下:
图一
红色代表我们要操作的环的一部分,容易发现与这个节点关联的环上的这两条边都是扩流操作,满足流量平衡。

再看看情况二,如下图所示:
图二
我们发现一个是扩流操作,另一个是缩流操作,无论这个环的方向如何,都满足流量平衡。

对于所有情况下一个环上的所有节点都满足流量平衡,因此一个环的操作不会破坏流量平衡,并且再不破坏流量平衡的情况下我们得到的花费就是环的权值和乘上一个系数 T T T,这个 T T T代表每条边被操作多少次(所有边肯定都要操作相同次数,否则流量不平衡),后面会发现这个 T T T不重要。

然后我们其实可以操作很多个环,每个环都操作若干次…

现在来看核心表达式 X − Y k \frac{X-Y}k kXY,分数规划的套路就是二分答案,我们现在想考察当前二分的答案值与该表达式的关系,假设满足 X − Y k ≥ m i d \frac{X-Y}k\ge mid kXYmid,也就是 k × m i d + Y − X ≤ 0 k\times mid+Y-X\le 0 k×mid+YX0 Y − X Y-X YX其实就是我们所说的若干个环的操作产生的花费, k k k的话其实就是所有边被操作的次数之和,不难发现一个环的话其实会更优,因为 X − Y k \frac{X-Y}k kXY其实就是所有边的平均花费,那么必定有些环的花费“被平均”(也就是说这些环其实花费可以更低),不如就取平均花费最小的那个环,肯定是更优的。

因此只需要找到一个环检验是否满足 k × m i d + Y − X ≤ 0 k\times mid+Y-X\le 0 k×mid+YX0即可,注意到 Y − X = ∑ 环 w i Y-X=\sum_{环} w_i YX=wi k k k的话其实一定是环的大小(边数),如果更大的话其实对应的只是每条边被多操作了几次,会约掉的。于是我们考虑让新图的所有边的边权加上 m i d mid mid,然后看是否存在负环即可。检测负环可以用 s p f a spfa spfa,我采用的是 d f s dfs dfs的写法,判断负环会比一般的要快一些。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 5e5+5;
const int maxm = 5e5+5;
const int inf = 2147483647;
typedef double db;
const db eps = 1e-3;


int h[maxn],ee=0,n,m,vis[maxn];
db d[maxn];
struct Edge{
	int v,next;db w;
	Edge(int v=0,db w=0,int next=0):v(v),w(w),next(next){}
}e[maxm];
void init(int n){
	memset(h,-1,sizeof(int)*(n+10));
	ee=0;
}
void addedge(int u,int v,db w){
	e[ee]=Edge(v,w,h[u]);
	h[u]=ee++;
}

bool spfa(int u,db x){
	vis[u]=1;
	for(int i=h[u];~i;i=e[i].next){
		int v=e[i].v;db w=e[i].w+x;
		if(d[v]>d[u]+w && fabs(d[v]-d[u]-w)>eps){
			d[v]=d[u]+w;
			if(vis[v] || spfa(v,x))return true;
		}
	}
	vis[u]=0;
	return false;
}
bool check(db x){
	for(int i=1;i<=n;++i)d[i]=0,vis[i]=0;
	for(int i=1;i<=n;++i)if(spfa(i,x))return true;
	return false;
}
int main(){
	scanf("%d%d",&n,&m);
	n+=2;
	init(n);
	for(int i=0;i<m;++i){
		int u,v,a,b,c,d;
		scanf("%d%d%d%d%d%d",&u,&v,&a,&b,&c,&d);
		if(c)addedge(v,u,a-d);
		addedge(u,v,b+d);
	}
	db l=0,r=1e9;
	while(fabs(r-l)>eps){
		db mid=(l+r)/2;
		if(check(mid)){
			l=mid;
		}else r=mid;
	}
	printf("%.2lf\n",l);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值