【上下界网络流学习小计】JZOJ3302.供电网络

33 篇文章 0 订阅
20 篇文章 0 订阅

Description

每个城市i多(缺)电量Left[i],可以耗费in[i]从外面输入电量,out[i]向外面输出电量,或者有一些有向边可以转移电量,这些有向边的流量有上界和下界,代价为a*x2+b,x为流量。求最少满足电量刚刚好为0的方案数。

Solution

  • 显然的上下界费用流题目。
  • 先考虑建图,再考虑上下界的问题。
  • 设源点S,汇点T。S向i或i向T连一个上界和下界都为|left[i]|的边,表示初值为此。
  • 源点向i连边,表示in[i],i向汇点连边,表示out[i]。
  • 原图中的有向边再根据上下界和费用连,将二次的代价拆一拆,则每流一次费用要改为a+b,3a+b,5a+b,所以要动态改路径上的边的费用。
  • 最后考虑上下界的问题。

上下界费用流

  • 网上有很多资料将上下界可行流讲的十分详细,但是上下界费用流的做法却缺少一些适合我的理解。
  • 对于上下界可行流,可以参考(https://www.cnblogs.com/liu-runda/p/6262832.html)(大佬%%%) 的理解和做法,即将下界所构成的网络和剩下的残量网络分开,根据每个点的入度和出度之差向超级源或超级汇连边,计算附加流,再将流量合并。
  • 如果有源汇就从汇点向源点连一条inf的边,使它变成无源汇。
  • 这样子连边边数自然比较少,但是我们注意到对于费用流,每一条边的费用是不一样的,难以将入度出度抵消,所以以上的做法并不适用。
  • 这题有另一种神奇的连法,然后跑最小费用最大流:有边x->y下界a,上界b。则改为连为ss->y[a],x->y[b-a],x->tt[a]。[x]表示流量是x。
  • ss->y和x->tt确定了下界为a,只有当ss以及tt都满流的时候才有可行流。
  • x->y确定了上界和下界之间的部分的贡献。
  • 为什么这样是对的?
  • 其实我也不会
  • 考虑如果有一条边,对于u,v的出度和入度分别有贡献,我们要求最后的图每一个点(除了超级源和超级汇)的出度都等于入度 (这样才能平衡)
  • 设定当且仅当ss的出边全部满流,tt的入边全部满流时,有可行流。
  • 那么每一条ss的出边都是下限,将y的入度+a;每一条tt的入边都是下限,将x的出度+a。这入度和出度的对应关系刚好同于x->y这条边对于入度出度造成的影响。再考虑x->y的b-a的边,每走一次就说明这条边在原图中的流量大了1。
  • 总之最终如果有可行流必定使得x和y的出度入度分别为下限到上限之间,而流量能够守恒,所以满足了条件。

关于哪条边有费用

  • 由于ss->y和x->tt必经过并且流量相等,可以假定它们一定是同时经过的,所以这两条边中只有一条有费用。
  • 由于x->y[b-a]表示的是在下限以上的多出来的部分,所以它的费用是跟下限部分的费用独立的,所以这条边也要有费用。
  • 另外,这道题的费用随着流量改变,所以ss->y的费用为流量在下限以内的费用,从a+b开始依次递增。但是x->y的费用已经假定走过了下限,所以从(2*low+1)*a+b开始递增。

这题的一些细节

  • 反向边的建立:反向边的费用要比正向边费用小一个2a,因为正向边还没有加,所反向边只能删去上一个正向边已经加过了的,所以小2a。
  • 走反向边之后费用不再+2a,因为反向边同于撤销操作,所以费用应该-2a,即还原回去。
  • 费用流我打的是EK,比较慢,但是方便每一次修改边的费用。
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#define maxn 205
#define maxm 10005
#define maxd 1005
#define inf 2e9
#define I(x) ((x&1)?x+1:x-1) 
using namespace std;

int n,m,i,j,k,x,y,a,b,L,U,left[maxn],in[maxn],out[maxn],ans;
int S,T,SS,TT;
int em,e[maxm],nx[maxm],ls[maxn],ec[maxm],va[maxm],vb[maxm],ct[maxm],tp[maxm];
int dis[maxn],vis[maxn],d[maxd],fa[maxn],fai[maxn],t,w;

void insert(int x,int y,int flow,int a,int b,int cnt){
	em++; e[em]=y; nx[em]=ls[x]; ls[x]=em; ec[em]=flow; va[em]=a; vb[em]=b; ct[em]=cnt;   tp[em]=1;
	em++; e[em]=x; nx[em]=ls[y]; ls[y]=em; ec[em]=0;    va[em]=a; vb[em]=b; ct[em]=cnt-2; tp[em]=-1;
}

void link(int x,int y,int low,int up,int v){
	insert(x,y,up-low,0,v,1);
	insert(SS,y,low,0,v,1);
	insert(x,TT,low,0,0,1);
}

int cost(int i){return (va[i]*ct[i]+vb[i])*tp[i];}

int spfa(){
	memset(dis,127,sizeof(dis));
	memset(vis,0,sizeof(vis));
	memset(fa,0,sizeof(fa));
	t=0,w=1;
	d[1]=TT,vis[TT]=1,dis[TT]=0;
	while (t<w){
		t=(t+1)%maxd;
		int x=d[t]; vis[x]=0;
		for(int i=ls[x];i;i=nx[i]) if (ec[I(i)]&&dis[x]+cost(I(i))<dis[e[i]]){
			dis[e[i]]=dis[x]+cost(I(i));
			fa[e[i]]=x; fai[e[i]]=i;
			if (!vis[e[i]]) w=(w+1)%maxd,vis[e[i]]=1,d[w]=e[i];
		}
	}
	return dis[SS]<inf;
}

void EKflow(){
	ans=0;
	while (spfa()){
		memset(vis,0,sizeof(vis));
		int x;
		for(x=SS;x!=TT&&!vis[x];x=fa[x]) {
			vis[x]=1;
			int i=fai[x],j=I(i);
			ec[i]++,ct[i]+=2*tp[j];
			ec[j]--,ct[j]+=2*tp[j];
		}
		ans+=dis[SS];
	}
}

int main(){
	scanf("%d%d",&n,&m);
	S=n+1,T=n+2,SS=n+3,TT=n+4;
	for(i=1;i<=n;i++) {
		scanf("%d%d%d",&left[i],&in[i],&out[i]);
		if (left[i]>=0) link(S,i,left[i],left[i],0);
		else link(i,T,-left[i],-left[i],0);
		link(S,i,0,inf,in[i]);
		link(i,T,0,inf,out[i]);
 	}
 	for(i=1;i<=m;i++) {
	 	scanf("%d%d%d%d%d%d",&x,&y,&a,&b,&L,&U);
	 	insert(SS,y,L,a,b,1);
	 	insert(x,y,U-L,a,b,2*L+1);
	 	insert(x,TT,L,0,0,1);
	}
	insert(T,S,inf,0,0,1);
	EKflow();
	printf("%d",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值