zoukankan      html  css  js  c++  java
  • 网络流题目选解

    这里有板子

    最大流

    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 imes 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
    ",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
    ",ans-dinic());
    	return 0;
    }
    

    [BJOI2016]水晶

    solution

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

    注意到这样转化后仍然有(x+y+zequiv x-z+y-zpmod 3)

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

    [x_1+y_1equiv 1pmod 3\ x_2+y_2equiv 0pmod 3\ x_3+y_3equiv 2pmod 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) 满足如果(uin S) ,则所有(u) 能到达的点都属于(S) 。而最大权闭合子图就是点权和最大的闭合子图。

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

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

    [NOI2009] 植物大战僵尸

    solution

    每个植物看作一个点,点权为其收益,如果植物(a) 保护植物(b) ,则连边(b ightarrow 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
    ",ans-dinic());
    	return 0;
    }
    

    Part 3

    一些杂题??

    [RC-02] 开门大吉

    description

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

    solution

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

    拆点。将每个人(i) 拆为(m+1) 个点,连边((i,j) ightarrow(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) ightarrow(i,x+k)(1le x,x+kle m+1)) 流量为(+infty) 。这样的话如果选择在((j,x) ightarrow (j,x+1)) 处割掉,那么由于(S ightarrow(j,1) ightarrow(j,x) ightarrow(i,x+k) ightarrow(i,m+1) ightarrow 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
    ",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
    ",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}^ksum_{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} imes 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
    ",dinic().second);
    	return 0;
    }
    

    [SCOI2012]奇怪的游戏

    solution

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

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

    (cb-cw ot=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
    ",(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
    ",(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 imes 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
    ",dinic().second+inf*sum);
    	return 0;
    }
    

    [SHOI2003]吃豆豆

    solution

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

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

    直接这样会超时,因为连的边太多。考虑优化。注意到如果(i ightarrow j,j ightarrow k) ,那么(i ightarrow 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
    ",dinic().second);
    	return 0;
    }
    
    NO PAIN NO GAIN
  • 相关阅读:
    原创:PHP编译安装配置参数说明
    原创 :xftp SFTP子系统申请已拒绝 请确保SSH链接的SFTP子系统设置有效
    原创:LNMP架构部署个人博客网站 禁止转载复制
    原创 :单刷深渊 在Linux中系统安装mysql实战直播
    原创:一键化部署百台服务器级别后端服务器
    原创 :nfs软件服务利用ansible实现一键化部署
    原创: rsync软件服务利用ansible实现一键化部署
    原创:100 台规模集群存储系统搭建及数据实时备份
    原创Couldn't read packet: Connection reset by peer 错误排查思路(推荐)
    原创:Docker在云家政的应用 谢绝复制粘贴内容
  • 原文地址:https://www.cnblogs.com/zmyzmy/p/14646040.html
Copyright © 2011-2022 走看看