ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

网络流题目选解

2021-10-29 15:00:07  阅读:184  来源: 互联网

标签:连边 code 题目 int 网络 tot 权值 选解 now


这里有板子

最大流

view code
namespace Flow
{
	int tot=1,fi[N],ne[M],to[M],w[M],S,T,d[N],nn;
	inline void add(int x,int y,int c)
	{
		ne[++tot]=fi[x],fi[x]=tot,to[tot]=y,w[tot]=c;
		ne[++tot]=fi[y],fi[y]=tot,to[tot]=x,w[tot]=0;
	}
	inline bool bfs()
	{
		fill(d+1,d+nn+1,-1);d[S]=0;
		queue<int>q;q.push(S);
		while(!q.empty())
		{
			int u=q.front();q.pop();
			for(int i=fi[u];i;i=ne[i])
			{
				int v=to[i];
				if(d[v]<0&&w[i])
					d[v]=d[u]+1,q.push(v);
			}
		}
		return d[T]!=-1;
	}
	int dfs(int u,int flow)
	{
		if(!flow||u==T)return flow;
		int used=0;
		for(int i=fi[u];i;i=ne[i])
		{
			int v=to[i];
			if(d[v]!=d[u]+1)continue;
			int t=dfs(v,min(w[i],flow-used));
			w[i]-=t,w[i^1]+=t;
			used+=t;
			if(used==flow)break;
		}
		if(used<flow)d[u]=-1;
		return used;
	}
	inline int dinic()
	{
		int e=0;
		while(bfs())e+=dfs(S,inf);
		return e;
	}
}
using namespace Flow;

费用流

view code
namespace Flow
{
	int tot=1,S,T,nn,fi[N],ne[M],to[M],c[M],pot[N],pre[N],f[N],w[M],dis[N];bool inq[N];
	inline void add(int x,int y,int s,int wt)
	{
		ne[++tot]=fi[x],fi[x]=tot,to[tot]=y,c[tot]=s,w[tot]=wt;
		ne[++tot]=fi[y],fi[y]=tot,to[tot]=x,c[tot]=0,w[tot]=-wt;
	}
	inline bool spfa()
	{
		fill(dis+1,dis+nn+1,-inf);dis[S]=0;
		queue<int>q;q.push(S);inq[S]=1;f[S]=inf;
		while(!q.empty())
		{
			int u=q.front();q.pop();inq[u]=0;
			for(int i=fi[u];i;i=ne[i])
			{
				int v=to[i];
				if(c[i]&&dis[v]<dis[u]+w[i])
				{
					dis[v]=dis[u]+w[i];
					pre[v]=u,pot[v]=i^1;
					f[v]=min(c[i],f[u]);
					if(!inq[v])q.push(v),inq[v]=1;
				}
			}
		}
		return dis[T]!=-inf;
	}
	inline pair<int,int> dinic()
	{
		int cost=0;int F=0;
		while(spfa())
		{
			int now=T,flow=f[T];F+=flow;
			while(now!=S)
			{
				cost+=w[pot[now]^1]*flow;
				c[pot[now]]+=flow;
				c[pot[now]^1]-=flow;
				now=pre[now];
			}
		}
		return make_pair(F,cost);
	}
}
using namespace Flow;

Part 1

一类网络流题目满足如下性质:

  • 有若干元素,每个元素有两种状态(黑或白,选或不选···),要求选出其中一种,且选择某一种有相应收益
  • 有若干限制或者额外收益,类似选择了\(A\) 的\(X\) 状态就不能选择\(B\) 的\(Y\) 状态或同时选择\(A\) 的\(X\) 状态和\(B\) 的\(Y\) 状态带来\(w\) 的额外收益。
  • 最后要求出收益最大值。

这种问题的套路都是先全部选择所有收益,然后减去构造出来的图的最小割(意义是不得不放弃的最小代价)(在此最小割中,在源点集合的选择一种状态,而在汇点集合的选择另外一种状态),即为最终答案。

这么说可能有些抽象,下面的题目均是此类型,便于理解。

[国家集训队]happiness

description

一个\(n\times m\) 座位表,每个座位上有一个同学。对于每个同学,选择文科或理科各有一定喜悦值。同时,对于位置相邻的两个同学,如果他们同时选择文科或理科各会带来额外的喜悦值。求最大喜悦值。

solution

考虑如何用最小割表示不得不放弃的最小代价

建立超级源点和超级汇点分别代表文科和理科。对于每个同学分别建点,如果在最小割中属于源点集合则代表他选择文科,否则选择理科。

源点向每个同学连接容量为选择文科喜悦值的边,每个同学向汇点连接容量为选择理科喜悦值的边。这样割去源点的边代表放弃文科带来的权值,最终选择了理科;割去汇点的边同理。

而后考虑额外权值。仍然是套路地,对于每一个额外权值建立新点,如果这个权值和选择文科相关,则由源点向它连接容量为权值的边,由它向对应同学连接容量为\(+\infty\) 的边。这样如果其中某位同学最终属于汇点集合(相当于选择理科),那么由汇点连出的边必然会断去,代表放弃了这个额外权值。

最终答案为初始权值和减去最小割。

code

view code
int n,m,ans,o;
inline int id(int x,int y){return (x-1)*n+y+2;}
int main()
{
	scanf("%d%d",&n,&m);S=1,T=2;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			scanf("%d",&o),ans+=o,add(S,id(i,j),o);
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			scanf("%d",&o),ans+=o,add(id(i,j),T,o);
	nn=n*m+2;
	for(int i=1;i<n;++i)
		for(int j=1;j<=m;++j)
		{
			int p=++nn;
			scanf("%d",&o);ans+=o;
			add(S,p,o),add(p,id(i,j),inf),add(p,id(i+1,j),inf);
		}
	for(int i=1;i<n;++i)
		for(int j=1;j<=m;++j)
		{
			int p=++nn;
			scanf("%d",&o);ans+=o;
			add(p,T,o),add(id(i,j),p,inf),add(id(i+1,j),p,inf);
		}
	for(int i=1;i<=n;++i)
		for(int j=1;j<m;++j)
		{
			int p=++nn;
			scanf("%d",&o);ans+=o;
			add(S,p,o),add(p,id(i,j),inf),add(p,id(i,j+1),inf);
		}
	for(int i=1;i<=n;++i)
		for(int j=1;j<m;++j)
		{
			int p=++nn;
			scanf("%d",&o);ans+=o;
			add(p,T,o),add(id(i,j),p,inf),add(id(i,j+1),p,inf);
		}
	printf("%d\n",ans-dinic());
	return 0;
}

方格取数问题

solution

这个是有限制的情况。

根据套路,先将所有的权值都选出来,然后用最小的代价删去一些点使得剩下的可行。

再转化一下,我们将一个方格和它相邻的方格连边,那么问题转化为了图的带权最大独立集。

根据黑白染色可以发现,这个图一定是一个二分图。这样的话就可做了。

由源点向黑点连边,容量为其权值;白点向汇点连边,容量为其权值。黑点连向相邻的白点,容量为\(+\infty\) 。这样任意一条从源点到汇点的路径就是一种不合法的情况,我们只需话最小代价割边使其不连通,符合最小割的定义。于是就做完了。

code

view code
int n,m,ans;
const int fx[]={0,0,1,-1},fy[]={1,-1,0,0};
inline bool ok(int x,int y){return x>=1&&x<=n&&y>=1&&y<=m;}
inline int id(int x,int y){return (x-1)*m+y;}
int main()
{
	scanf("%d%d",&n,&m);nn=n*m;
	S=++nn,T=++nn;int d;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
		{
			scanf("%d",&d);ans+=d;
			int now=id(i,j);
			if((i+j)&1)add(S,now,d);
			else{add(now,T,d);continue;}
			for(int k=0;k<4;++k)
			{
				int x=i+fx[k],y=j+fy[k];
				if(ok(x,y))add(now,id(x,y),inf);
			}
		}
	printf("%d\n",ans-dinic());
	return 0;
}

[BJOI2016]水晶

solution

原题坐标很烦,因为坐标和位置并不是一一对应的。于是可以这样转化\((x,y,z)\rightarrow(x-z,y-z)\) (原因:矢量加法遵从平行四边形定则)。平面钝角坐标系???

注意到这样转化后仍然有\(x+y+z\equiv x-z+y-z\pmod 3\) 。

不难发现\(a\) 共振和\(b\) 共振一起相当于不能存在相邻的三个位置\((x_1,y_1),(x_2,y_2),(x_3,y_3)\) 满足

\[x_1+y_1\equiv 1\pmod 3\\ x_2+y_2\equiv 0\pmod 3\\ x_3+y_3\equiv 2\pmod 3\\ \]

于是对于每个水晶先拆点,分为入点和出点,由入点向出点连容量为权值的边。

然后将水晶按照横纵坐标数值之和除以\(3\) 的余数分类。模\(3\) 为\(1\) 的向相邻的模\(3\) 为\(0\) 的连边,模\(3\) 为\(0\) 的向相邻的模\(3\) 为\(2\) 的连边,容量均为\(+\infty\) 。

然后源点向模\(3\) 为\(1\) 的点连边,模\(3\) 为\(2\) 的点向汇点连边,容量均为\(+\infty\) 。

和上一道题类似的分析方法,最终答案为总权值减取最小割。

注意:1.此题中相邻和别的题目中的定义略有不同。2.同一个点中有多个水晶的情况需要特殊处理。

code

view code
const int fx[]={0,0,1,-1,1,-1},fy[]={1,-1,0,0,1,-1};
map<pair<int,int>,int>mp,buc;
int x[P],y[P],tp[P],n,ans;
int main()
{
	scanf("%d",&n);
	for(int i=1,z,v;i<=n;++i)
	{
		scanf("%d%d%d%d",&x[i],&y[i],&z,&v);
		x[i]-=z,y[i]-=z;
		tp[i]=(x[i]+y[i])%3;tp[i]=(tp[i]+3)%3;
		v*=10;if(!tp[i])v=v/10*11;ans+=v;
		buc[make_pair(x[i],y[i])]+=v;
	}
	n=buc.size();int now=0;
	for(auto it=buc.begin();it!=buc.end();it++)
	{
		++now,add(now,now+n,it->second);
		x[now]=(it->first).first,y[now]=(it->first).second;
		tp[now]=(x[now]+y[now])%3;tp[now]=(tp[now]+3)%3;
		mp[it->first]=now;
	}
	nn=2*n;S=++nn;T=++nn;
	for(int i=1;i<=n;++i)
	{
		if(tp[i]==2)add(i+n,T,inf);
		else if(tp[i]==1)
		{
			add(S,i,inf);
			for(int k=0;k<6;++k)
			{
				int nx=x[i]+fx[k],ny=y[i]+fy[k];
				if(!mp.count(make_pair(nx,ny)))continue;
				int id=mp[make_pair(nx,ny)];
				if(tp[id])continue;
				add(i+n,id,inf);
			}
		}
		else
		{
			for(int k=0;k<6;++k)
			{
				int nx=x[i]+fx[k],ny=y[i]+fy[k];
				if(!mp.count(make_pair(nx,ny)))continue;
				int id=mp[make_pair(nx,ny)];
				if(tp[id]!=2)continue;
				add(i+n,id,inf);
			}
		}
	}
	printf("%.1lf",(ans-dinic())/10.0);
	return 0;
}

Part 2

最大权闭合子图问题 ,它的描述是这样的:

对于一个点带权的\(DAG\) 图,定义其闭合子图为一个点集\(S\) 满足如果\(u\in S\) ,则所有\(u\) 能到达的点都属于\(S\) 。而最大权闭合子图就是点权和最大的闭合子图。

如何解决??还是沿袭先前的套路,先选取所有的正权点的权值,然后建立最小割模型,由源点向正权点连接容量为其权值的边,负权点向汇点连接容量为其权值相反数的边,然后原图的边均保留,容量为\(+\infty\) 。

最后答案为所有的正权点的权值减去最小割。在最小割中,没有被割的连向正权点的边表示选择了这个点,被割的由负权点连出的边表示选择了这个点,不难发现这样做一定是满足条件的。

[NOI2009] 植物大战僵尸

solution

每个植物看作一个点,点权为其收益,如果植物\(a\) 保护植物\(b\) ,则连边\(b\rightarrow a\) 表示将\(a\) 吃掉后吃掉\(b\) 。

注意这样建出来的是有环的,而方才介绍的模型是不允许有环的。于是拓扑排序去除其中的环然后套用上面的模型即可。

注意拓扑排序建出的图恰好是我们需要建出的图的反图,稍微注意下。

code

view code
int n,m,val[P*P],deg[P*P];bool flag[P*P],G[P*P][P*P];
inline int id(int x,int y){return (x-1)*m+y;}
vector<int>e[P*P];
inline void adde(int x,int y){e[x].push_back(y);}
inline void topo()
{
	nn=n*m;
	for(int i=1;i<=nn;++i)
		for(int v:e[i])++deg[v];
	queue<int>q;
	for(int i=1;i<=nn;++i)
		if(!deg[i])q.push(i),flag[i]=1;
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(int v:e[u])
			if(!(--deg[v]))
				flag[v]=1,q.push(v);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
		for(int j=1,w,x,y;j<=m;++j)
		{
			int now=id(i,j);
			scanf("%d%d",&val[now],&w);
			while(w--)
				scanf("%d%d",&x,&y),
				adde(now,id(x+1,y+1));
		}
	for(int i=1;i<=n;++i)
		for(int j=2;j<=m;++j)
			adde(id(i,j),id(i,j-1));
	topo();
	for(int u=1;u<=nn;++u)
	{
		if(!flag[u])continue;
		for(int v:e[u])
			if(flag[v]&&!G[v][u])add(v,u,inf),G[v][u]=1;
	}
	S=++nn,T=++nn;int ans=0;
	for(int u=1;u<=n*m;++u)
	{
		if(!flag[u])continue;
		if(val[u]>0)add(S,u,val[u]),ans+=val[u];
		else add(u,T,-val[u]);
	}
	printf("%d\n",ans-dinic());
	return 0;
}

Part 3

直接看例题。

最长k可重区间集问题

solution

该模型较为经典,因此特写一篇题解以加深印象。

容易使人联想到是费用流,但如何建模才是关键。我们可以将整个过程做如下转化:初始时有\(k\) 个人,\(n\) 件工作。每件工作的持续时间为\((l_i,r_i)\) ,仅需要一个人做且这个人做这件工作时不能做其他工作,而完整地做完这件工作可以获得\(w_i\) 的收益。现在需要你合理地安排使得收益之和最大。该问题只是原问题的另一种形式,本质相同,但是建模却方便地多:即建立超级源点向数轴上0的位置连边,流量为\(k\) 费用为0。然后对于数轴上每个点\(i\) 向\(i+1\) 连边,流量为\(k\) 费用为0。然后从数轴上最大的点向超级汇点连边。最后对于每个区间,从\(l_i\) 向\(r_i\) 连边,流量为1费用为\(w_i\) 。跑出最大费用可行流即为答案。值域比较大时可以先离散化。

最长k可重线段集问题

solution

本题和上一题本质相同,只有处理垂直于\(x\) 轴线段不太相同。对于这种问题,固然可以直接拆点,但是更加简单的方式是扩域。相当于我们现在不只有整点,还有诸如\(\dfrac d2\) (\(d\) 为奇数)这类点。为了仍保持原来的相交/不相交关系,只需要将\(l=r\) 的\((l,r)\) 变为\((l,r+0.5)\) ,\(l\not=r\) 的\((l,r)\) 变为\((l+0.5,r)\) 即可。然后再将坐标同统一\(\times 2\) 就和先前的题完全一致了。

Part 4

一些杂题??

[RC-02] 开门大吉

description

有\(n\) 个人,\(m\) 套题,第\(i\) 个人去做第\(j\) 套题会产生\(g_{i,j}\) 的权值。现在还有若干要求,每个要求形如\(i\ j\ k\) 表示第\(i\) 个人做的题的编号至少比第\(j\) 个人做的题目编号大\(k\) 。现在要给每个人分配一套题使得总权值尽量小。

solution

总权值最小联想到最小割模型。

拆点。将每个人\(i\) 拆为\(m+1\) 个点,连边\((i,j)\rightarrow(i,j+1)\) 流量为\(g_{i,j}\) ,割去这条边表示第\(i\) 个人做第\(j\) 套题产生的代价。

另外,因为一个人只能做一套题,为了保证对于一个人只割一条边,我们还需要连边\((i,j)\leftarrow (i,j+1)\) 边权为\(+\infty\) 。这样的话如果最小割中被割了两次,则会引出矛盾:

因为是最小割,所以割的两条边必须有用,也就是它们前面与源点连通,后面与汇点连通。然而,因为有反向边,则后面的那个与源点连通,然后就可以走过去,走到汇点,与是一个割矛盾。

然后源点向所有的\((i,1)\) 连边,流量为\(+\infty\) ;\((i,m+1)\) 向汇点连边,流量为\(+\infty\) 。

现在的关键在于如何满足要求。

对于要求\((i,j,k)\) ,我们需要连边\((j,x)\rightarrow(i,x+k)(1\le x,x+k\le m+1)\) 流量为\(+\infty\) 。这样的话如果选择在\((j,x)\rightarrow (j,x+1)\) 处割掉,那么由于\(S\rightarrow(j,1)\rightarrow(j,x)\rightarrow(i,x+k)\rightarrow(i,m+1)\rightarrow T\) 这条路径的存在,则必须在\(\ge x+k\) 的地方割掉,满足了要求。

code

view code
int n,m,p,y,c[P];
db a[P][P];
inline int id(int x,int y){return (x-1)*(m+1)+y;}
int main()
{
	int t;scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d%d%d",&n,&m,&p,&y);
		for(int i=1;i<=p;++i)scanf("%d",c+i),c[i]+=c[i-1];
		for(int j=1;j<=m;++j)
			for(int i=1;i<=n;++i)
			{
				a[i][j]=0;db now=1.0,x;
				for(int k=1;k<=p;++k)
				{
					scanf("%lf",&x);
					a[i][j]+=c[k-1]*now*(1-x);
					now*=x;
				}
				a[i][j]+=c[p]*now;
			}
		pre(n*(m+1)+2);
		for(int i=1;i<=n;++i)
		{
			add(S,id(i,1),inf),add(id(i,m+1),T,inf);
			for(int j=1;j<=m;++j)
				add(id(i,j),id(i,j+1),a[i][j]),
				add(id(i,j+1),id(i,j),inf);
		}
		while(y--)
		{
			int x,y,k;scanf("%d%d%d",&x,&y,&k);
			for(int i=1;i<=m;++i)
				if(i+k>=1&&i+k<=m+1)
					add(id(y,i),id(x,i+k),inf);
		}
		db ans=dinic();
		if(sig(inf-ans)<=0)puts("-1");
		else printf("%.4lf\n",ans);
	}
	return 0;
}

CF277E Binary Tree on Plane

solution

拆点。一个点拆为入点和出点。

源点向所有入点连接容量为\(2\) 费用为\(0\) 的边代表该节点至多两个儿子。

所有出点向汇点连边,容量为\(1\) 费用为\(0\) 代表该节点至多有一个父亲。

然后对于一个点\(i\) ,它的入点向所有可以作为它儿子\(j\) 的出点连接容量为\(1\) ,费用为其距离的边,代表这条边至多选择一起,且代价为其距离。

最后跑最小费用最大流。注意只有根没有父亲,所以当且仅当最大流为\(n-1\) 时才有解。

code

view code
int n,x[P],y[P];
inline int sq(int a){return a*a;}
inline db d(int a,int b){return sqrt(sq(x[a]-x[b])+sq(y[a]-y[b]));}
int main()
{
	scanf("%d",&n);init(2*n+2);
	for(int i=1;i<=n;++i)
		scanf("%d%d",x+i,y+i);
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			if(y[j]<y[i])
				add(i,j+n,1,d(i,j));
	for(int i=1;i<=n;++i)
		add(S,i,2,0),add(i+n,T,1,0);
	auto ans=dinic();
	if(ans.first<n-1)puts("-1");
	else printf("%.15lf\n",ans.second);
	return 0;
}

[NOI2012] 美食节

solution

首先需要将总等待时间进行转化,假设现在只有一个师傅,所需做的菜所花的时间依次为\(t_1,t_2,\cdots t_k\) ,那么总等待时间为

\[\sum_{i=1}^kt_i+\sum_{i=2}^kt_i+\cdots \sum_{i=k}^kt_i=\sum_{j=1}^k\sum_{i=j}^kt_i=\sum_{i=1}^kt_i(k-i+1) \]

说人话就是如果当前这个菜是倒数第\(p\) 个做的,那么对总等待时间的贡献为\(pt\)

于是就可以开开心心地建图了。记\(sum=\sum_{i=1}^np_i\) ,那么将每个厨师\(i\) 拆为\(sum\) 个点代表厨师\(i\) 做倒数第\(j\) 个菜。那么由源点向每一种菜连边,容量为\(p_i\) ,费用为\(0\) ;每种菜向刚才拆的点(厨师\(j\) 做倒数第\(k\) 个菜)连边,容量为\(1\),费用为\(t_{j,k}\times k\) ;这些点再向汇点连容量为\(1\) ,费用为\(0\) 的边。最后跑一个最小费用最大流就是答案。

这个模型可能可以解决不少类似问题吧。

但对于此题来讲,直接这样建图会\(T\) 。考虑优化。其实我们根本不用拆那么多点,因为不可能每个厨师都有\(sum\) 个菜做。注意到这样一个性质:对于同一个厨师,一定是先走倒数第\(k\) 个做菜,然后菜有可能走倒数第\(k+1\) 个做菜。于是乎初始时只用连倒数第一道菜的边,然后增广一次我们就检查每一位厨师看他是否做完了倒数第一道菜,如果是,再增加倒数第二道菜的边,以此类推。类似于数据结构的动态开点

code

view code
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=1e7+5,inf=0x3f3f3f3f,P=105;
int m,n,t[P][P],p[P],sum,now[P],id[P];
namespace Flow
{
	int tot=1,S,T,nn,fi[N],ne[M],to[M],c[M],pot[N],pre[N],f[N],w[M],dis[N];bool inq[N];
	inline void add(int x,int y,int s,int wt)
	{
		ne[++tot]=fi[x],fi[x]=tot,to[tot]=y,c[tot]=s,w[tot]=wt;
		ne[++tot]=fi[y],fi[y]=tot,to[tot]=x,c[tot]=0,w[tot]=-wt;
	}
	inline void adde(int i,int pos)
	{
		int cur=++nn;
		for(int k=1;k<=n;++k)
			add(k+2,cur,inf,t[i][k]*pos);
		add(cur,T,1,0);now[i]=pos;id[i]=cur;
	}
	inline bool spfa()
	{
		fill(dis+1,dis+nn+1,inf);dis[S]=0;
		queue<int>q;q.push(S);inq[S]=1;f[S]=inf;
		while(!q.empty())
		{
			int u=q.front();q.pop();inq[u]=0;
			for(int i=fi[u];i;i=ne[i])
			{
				int v=to[i];
				if(c[i]&&dis[v]>dis[u]+w[i])
				{
					dis[v]=dis[u]+w[i];
					pre[v]=u,pot[v]=i^1;
					f[v]=min(c[i],f[u]);
					if(!inq[v])q.push(v),inq[v]=1;
				}
			}
		}
		return dis[T]!=inf;
	}
	inline pair<int,int> dinic()
	{
		int cost=0,F=0;
		while(spfa())
		{
			int now=T,flow=f[T];F+=flow;
			while(now!=S)
			{
				cost+=w[pot[now]^1]*flow;
				c[pot[now]]+=flow;
				c[pot[now]^1]-=flow;
				now=pre[now];
			}
			for(int u=1;u<=m;++u)
				for(int i=fi[id[u]];i;i=ne[i])
				{
					int v=to[i];if(v!=T)continue;
					if(c[i])continue;
					adde(u,::now[u]+1);break;
				}
		}
		return make_pair(F,cost);
	}
}
using namespace Flow;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)scanf("%d",p+i),sum+=p[i];
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
			scanf("%d",&t[j][i]);
	S=1,T=2;
	for(int i=1;i<=n;++i)add(S,i+2,p[i],0);nn=n+2;
	for(int i=1;i<=m;++i)adde(i,1);
	printf("%d\n",dinic().second);
	return 0;
}

[SCOI2012]奇怪的游戏

solution

黑白染色。假设黑色格子有\(cb\) 个,权值和为\(sb\) ,白色格子有\(cw\) 个,权值和为\(sw\) 。每次操作一定是黑色白色权值各加\(1\) 。

假设最终的同一个数为\(X\) ,那么就有\(sb-sw=X(cb-cw)\)

\(cb-cw\not=0\) 当且仅当\(n,m\) 均为奇数。此时可以计算出\(X\) 的值,然后最大流随便\(check\) 一下就行。

否则\(n,m\) 中至少一个为偶数。此时倘若\(X\) 可行,那么\(X+1\) 必然也可行。于是二分答案,还是用最大流\(check\) 。

\(check\) 类似于二分图匹配的过程,最后判断是否满流即可。

view code
const int fx[]={0,0,1,-1},fy[]={1,-1,0,0};
int n,m,mp[P][P];ll sb,sw,mx;
inline bool ok(int x,int y){return x>=1&&x<=n&&y>=1&&y<=m;}
inline int id(int x,int y){return (x-1)*m+y;}
inline bool ck(ll x)
{
	clear();ll sum=0;
	S=n*m+1,T=S+1;nn=T;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=m;++j)
		{
			if(!((i+j)&1)){add(id(i,j),T,x-mp[i][j]);continue;}
			add(S,id(i,j),x-mp[i][j]);sum+=x-mp[i][j];
			for(int k=0;k<4;++k)
			{
				int x=i+fx[k],y=j+fy[k];
				if(ok(x,y))add(id(i,j),id(x,y),inf);
			}
		}
	ll ret=dinic();
	return ret==sum;
}
int main()
{
	int t;scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);mx=sb=sw=0;
		for(int i=1;i<=n;++i)
			for(int j=1;j<=m;++j)
			{
				scanf("%d",&mp[i][j]);
				((i+j)&1)?sb+=mp[i][j]:sw+=mp[i][j];
				mx=max(mx,0ll+mp[i][j]);
			}
		if((n*m)&1)
		{
			ll ans=sw-sb;
			if(ans<mx||!ck(ans))puts("-1");
			else printf("%lld\n",(1ll*n*m*ans-sw-sb)/2);
			continue;
		}
		if(sb!=sw){puts("-1");continue;}
		ll l=mx-1,r=5e9;
		while(l+1<r)
		{
			ll mid=(l+r)>>1;
			ck(mid)?r=mid:l=mid;
		}
		printf("%lld\n",(1ll*n*m*r-sw-sb)/2);
	}
	return 0;
}

80人环游世界

solution

考虑将每个国家拆点,分为入点和出点,之间连接容量为\(v_i\) 的边。

但是这样做并不能保证每个国家恰好经过\(v_i\) 次,于是将其费用令为\(-\infty\) ,这样就能诱导它把容量流完。

然后对于从\(i\) 到\(j\) 的机票价值为\(w\) ,我们连接\(i\) 的出点和\(j\) 的入点,容量为\(+\infty\) ,费用为\(w\) 。

建一个点向所有入点连边,容量\(+\infty\) ,费用为\(0\) ,代表开始可以从任意位置出发。源点向这个点连容量为\(m\) 的边。

所有出点向汇点连边,容量\(+\infty\) ,费用为\(0\) ,代表开始可以从任意位置结束。

假设最后最小费用最大流跑出来的答案为\(ans\) ,那么最终答案为\(ans+sum\times \infty\) ,其中\(sum=\sum v_i\)

code

view code
int n,m,x,sum;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)
		scanf("%d",&x),add(i,i+n,x,-inf),sum+=x;
	nn=n*2;
	for(int i=1;i<n;++i)
		for(int j=i+1;j<=n;++j)
		{
			scanf("%d",&x);
			if(x<0)continue;
			add(i+n,j,_inf,x);
		}
	S=++nn;
	for(int i=1;i<=n;++i)add(S,i,_inf,0);
	add(++nn,S,m,0);S=nn;
	T=++nn;
	for(int i=1;i<=n;++i)add(i+n,T,_inf,0);
	printf("%lld\n",dinic().second+inf*sum);
	return 0;
}

[SHOI2003]吃豆豆

solution

首先路径不能相交没有任何用处,因为如果相交了,那么在相交点两人分别掉头即可,不会影响最终答案。

有一种显然的建图方式:将每个豆豆拆点,由入点向出点连两条边,一条流量为\(1\) 费用为\(1\) ,另一条流量为\(+\infty\) ,费用为\(0\) ,代表这个点可以经过多次但只有一次会计算贡献。然后合法的点对间出点连向入点即可。最后跑最大费用最大流。

直接这样会超时,因为连的边太多。考虑优化。注意到如果\(i\rightarrow j,j\rightarrow k\) ,那么\(i\rightarrow k\) 这条边是没有必要的。于是可以排一遍序然后随便搞一波就行。(这里并没有最小化连的边的数量,只是比暴力连边稍微好了些,应该还是可能会被卡

code

view code
struct pt{int x,y;}p[P];
inline bool operator<(const pt&x,const pt&y){return x.x!=y.x?x.x<y.x:x.y<y.y;}
int n;
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)scanf("%d%d",&p[i].x,&p[i].y);
	sort(p+1,p+n+1);
	nn=n*2;
	for(int i=1;i<=n;++i)add(i,i+n,1,1),add(i,i+n,inf,0);
	S=++nn;
	for(int i=1;i<=n;++i)add(S,i,inf,0);
	add(++nn,S,2,0);S=nn;
	T=++nn;
	for(int i=1;i<=n;++i)add(i+n,T,inf,0);
	for(int i=1;i<=n;++i)
	{
		int now=inf;
		for(int j=1;j<=n;++j)
		{
			if(i==j||p[j].x<p[i].x||p[j].y<p[i].y)continue;
			if(now>=p[j].y)add(i+n,j,inf,0),now=p[j].y;
		}
	}
	printf("%d\n",dinic().second);
	return 0;
}

标签:连边,code,题目,int,网络,tot,权值,选解,now
来源: https://www.cnblogs.com/zmyzmy/p/14646040.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有