【CF704D】Captain America(上下界网络流)

点此看题面

  • 平面上有(n)个点,你需要把每个点染色,每染一个红点需要(r)的代价,每染一个蓝点需要(b)的代价。
  • (m)个限制,形如直线(x=i/y=i)上两种颜色点数差不能超过(d)
  • 求构造一种染色方案,使得代价最小。
  • (n,mle10^5)

网络流模型

不妨假设(rle b),那么我们肯定尽可能选择染红色。

对于一行,假设它有(c)个点,最紧的限制为(s),那么就相当于可以选择(lceilfrac {c-s}2 ceilsimlfloorfrac{c+s}2 floor)个红点。

由于每一个点有两个坐标,它受到二重约束。

因此我们可以在靠近源点的方向为(x=i)的限制建一排点,在靠近汇点的方向为(y=i)的限制建一排点,这些点分别与源点和汇点连边限制流量,而平面上的每个点((x_0,y_0))都可以看作从左侧(x=x_0)的点连向右侧(y=y_0)的点的一条容量为(1)的边。

这样一来就建立起一个网络流模型,而且这是一张二分图。

有源汇有上下界最大流

注意到这里的有些边是有下界的限制的。

解决方案可参照这道板子题:【LOJ116】有源汇有上下界最大流

最后要输出方案,只要看点对应的边是否被流过,被流过(剩余流量为(0))说明是红色。

代码:(O(nsqrt n))

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define INF (int)1e9 
#define NA() (puts("-1"),exit(0),0)
using namespace std;
int n,m,r,b,op,xx[N+5],yy[N+5],cx[N+5],cy[N+5],sx[N+5],sy[N+5],d[2*N+5];
int tx;map<int,int> wx;I int px(CI x) {return wx.count(x)?wx[x]:wx[x]=++tx;}
int ty;map<int,int> wy;I int py(CI y) {return wy.count(y)?wy[y]:wy[y]=++ty;}
namespace D//有源汇有上下界网络流
{
	#define PS (2*N+4)
	#define ES (3*N)
	#define s0 (tx+ty+1)
	#define t0 (tx+ty+2)
	#define ss (tx+ty+3)
	#define tt (tx+ty+4)
	#define adde(x,y,f) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].F=f)
	int ee=1,lnk[PS+5],cur[PS+5];struct edge {int to,nxt,F;}e[2*ES+5];
	I void Add(CI x,CI y,CI f) {adde(x,y,f),adde(y,x,0);}
	int q[PS+5],d[PS+5];I bool BFS(CI s,CI t)
	{
		RI i,k,H,T;for(i=1;i<=tt;++i) d[i]=0;d[q[H=T=1]=s]=1;W(H<=T&&!d[t])
			for(i=lnk[k=q[H++]];i;i=e[i].nxt) !d[e[i].to]&&e[i].F&&(d[q[++T]=e[i].to]=d[k]+1);
		return memcpy(cur,lnk,sizeof(lnk)),d[t];
	}
	I int DFS(CI x,CI t,RI f=INF)
	{
		if(x==t||!f) return f;RI i,o,g=0;for(i=cur[x];i;i=e[i].nxt)
		{
			if((d[x]+1)^d[e[i].to]||!e[i].F||!(o=DFS(e[i].to,t,min(f,e[i].F)))) continue;
			if(e[i].F-=o,e[i^1].F+=o,g+=o,!(f-=o)) break;
		}return cur[x]=i,g;
	}
	I bool Try() {W(BFS(ss,tt)) DFS(ss,tt);for(RI i=lnk[ss];i;i=e[i].nxt) if(e[i].F) return 0;return 1;}//用虚拟源汇跑可行流
	I int MaxFlow() {RI g=0;W(BFS(s0,t0)) g+=DFS(s0,t0);return g;}//用实际源汇跑最大流
	I void Print() {for(RI i=1;i<=n;++i) putchar(e[i<<1].F^op?'b':'r');}//根据是否有流量剩余判断颜色
}
int main()
{
	RI i,x,y;scanf("%d%d%d%d",&n,&m,&r,&b),r>b&&(swap(r,b),op=1);
	for(i=1;i<=n;++i) scanf("%d%d",xx+i,yy+i),++cx[xx[i]=px(xx[i])],++cy[yy[i]=py(yy[i])];//统计每条直线上的点数
	for(i=1;i<=tx;++i) sx[i]=cx[i];for(i=1;i<=ty;++i) sy[i]=cy[i];for(i=1;i<=n;++i) D::Add(xx[i],tx+yy[i],1);//把每个点变成两条直线对应点间的连边
	RI op;for(i=1;i<=m;++i) scanf("%d%d%d",&op,&x,&y),
		op==1?wx.count(x)&&(sx[wx[x]]=min(sx[wx[x]],y)):wy.count(x)&&(sy[wy[x]]=min(sy[wy[x]],y));//记录限制
	for(i=1;i<=tx;++i) (x=cx[i]-sx[i]+1>>1)>(y=cx[i]+sx[i]>>1)?NA():(D::Add(s0,i,y-x),d[s0]-=x,d[i]+=x);//建边有上下界
	for(i=1;i<=ty;++i) (x=cy[i]-sy[i]+1>>1)>(y=cy[i]+sy[i]>>1)?NA():(D::Add(tx+i,t0,y-x),d[tx+i]-=x,d[t0]+=x);//建边有上下界
	for(i=1;i<=t0;++i) d[i]>0&&(D::Add(ss,i,d[i]),0),d[i]<0&&(D::Add(i,tt,-d[i]),0);D::Add(t0,s0,INF);//根据强制度数与源汇连边
	return D::Try()?(printf("%lld
",1LL*D::MaxFlow()*(r-b)+1LL*n*b),D::Print(),0):puts("-1"),0;//上下界网络流
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/CF704D.html