Pearblossom

朢罗柳

网络流

最小割

有一些比较典的trick:

  • 如果每个点只有两种可能(比如说选或不选),那么我们可以定义:与源点相连的代表选,与汇点相连的代表不选,根据这样的定义建图(集合划分模型)

  • 每个点的利润有冲突的话,可以将这两个点连一条 \(inf\) 边代表矛盾

  • 如果选了 \(i\) 点以后就对其它点的选择有限制,设为 \(j\) 点(有点不能选了),可以通过连边使得 \(i\)\(j\) 都被割去后源点与汇点仍然联通实现 \(i\)\(j\) 不能同时被割去

看题吧

P2774 方格取数问题

题目描述

有一个 m 行 n 列的方格图,每个方格中都有一个正整数。现要从方格中取数,使任意两个数所在方格没有公共边,且取出的数的总和最大,请求出最大的和。

点击查看代码 思路显然,如果选择了$(x,y)$,那就不能选择它的上下左右 $4$ 个点,将一个格点分成两个点,一个与源点相连,一个与汇点连边,矛盾的点对就左连右。讲究看吧。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int X[]={1,-1,0,0},Y[]={0,0,1,-1},inf=1e9;
int n,m,S,T,a[105][105],ans,tot=1,head[(int)2e6+10];
struct edge{int to,nex,w;}e[(int)2e6+100];
int dis[(int)2e6+100];
void Add(int u,int v,int w)
{e[++tot]={v,head[u],w};head[u]=tot;}
inline int id(int x,int y,int tag)
{return (x-1)*n+y+tag*n*m;}
queue<int>q;
bool bfs() {
	for(int i=1;i<=T;i++) dis[i]=0;
	dis[S]=1;q.push(S);
	while(q.size()) {
		int u=q.front();q.pop();
		for(int i=head[u];i;i=e[i].nex) {
			int v=e[i].to;
			if(e[i].w>0&&dis[v]==0) {
				dis[v]=dis[u]+1;
				q.push(v);
			}
		}
	}
	return dis[T]>0;
}
int dfs(int u,int flow) {
	if(u==T||flow==0) return flow;
	int f=0;
	for(int i=head[u];i&&flow;i=e[i].nex) {
		int v=e[i].to;
		if(e[i].w>0&&dis[v]==dis[u]+1) {
			int op=dfs(v,min(flow,e[i].w));
			if(op==0) dis[v]=0;
			f+=op;flow-=op;
			e[i].w-=op;e[i^1].w+=op;
		}
	}
	return f;
}
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>m>>n;S=n*m*2+1;T=n*m*2+2;
	for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) cin>>a[i][j];
	int sum=0;
	for(int i=1;i<=m;i++) {
		for(int j=1;j<=n;j++) {
			for(int t=0;t<4;t++) {
				int tx=i+X[t],ty=j+Y[t];
				if(tx<1||tx>m||ty<1||ty>n) continue;
				Add(id(i,j,0),id(tx,ty,1),inf);
				Add(id(tx,ty,1),id(i,j,0),0);
			}
			Add(S,id(i,j,0),a[i][j]);
			Add(id(i,j,0),S,0);
			sum+=a[i][j];
			Add(id(i,j,1),T,a[i][j]);
			Add(T,id(i,j,1),0);
		}
	}
	while(bfs()) ans+=dfs(S,inf);
	cout<<(sum-ans/2)<<'\n';
	return 0;
}

P1646 [国家集训队] happiness

题目描述

有一个 \(n*m\) 的矩阵代表学生,学生选文选理都有一定的喜悦值,如果与相邻的人的选科相同则还会有额外的喜悦值,求所有选科方案中最大的喜悦值。

点击查看代码 有不止一种连边方法,先讲题解中的。

先不考虑额外的喜悦值,只看单个人的喜悦值,那与源点相连代表选文,与汇点相连代表选理,因为选文和选理矛盾。接着加上相邻人的组合的喜悦值,两人同文的情况与其中某一人选理的情况矛盾,所以可以新建一个节点代表两人同文。

再说一说我想出来的连边方法:将每一个点都拆成两个点,\(i\)\(S\) 通过一条边权为 \(wen[i]\) 的边相连,\(i+n\)\(T\) 通过一条边权为 \(li[i]\) 的边相连,至于额外的贡献,就再建两个新节点分别代表同文和同理,矛盾的点对之间连一条 \(inf\) 边表示即可。


#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e17+7;
int n,m,wen[105][105],li[105][105];
int twen[105][105][2][2],tli[105][105][2][2];
int ans,sum,S,T,head[(int)2e6+100],cur[(int)2e6+100];
int tot=1,dis[(int)2e6+100];
struct edge{int to,nex,w;}e[(int)2e6+100];
inline int ID(int x,int y,int tag)
{return (x-1)*(m+2)+y+1+(n+2)*(m+2)*tag;}
void Add(int u,int v,int w)
{e[++tot]={v,head[u],w};head[u]=tot;}
bool bfs() {
	queue<int>q;
	for(int i=1;i<=T;i++) dis[i]=0,cur[i]=head[i];
	dis[S]=1;q.push(S);
	while(q.size()) {
		int u=q.front();q.pop();
		for(int i=head[u];i;i=e[i].nex) {
			int v=e[i].to;
			if(e[i].w>0&&dis[v]==0) {
				dis[v]=dis[u]+1;
				q.push(v);
			}
		}
	}
	return dis[T]>0;
}
int dfs(int u,int flow) {
	if(u==T||flow==0) return flow;
	int f=0;
	for(int i=cur[u];i&&flow;i=e[i].nex) {
		int v=e[i].to;cur[u]=i;
		if(dis[v]==dis[u]+1&&e[i].w>0) {
			int op=dfs(v,min(e[i].w,flow));
			if(op==0) dis[v]=0;
			f+=op;flow-=op;
			e[i].w-=op;e[i^1].w+=op;
		}
	}
	return f;
}
signed main() {
	cin>>n>>m;
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++) 
	cin>>wen[i][j],ans=ans+wen[i][j];
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)
	cin>>li[i][j],ans=ans+li[i][j];
	for(int i=1;i< n;i++)for(int j=1;j<=m;j++)
	cin>>twen[i][j][1][0],ans=ans+twen[i][j][1][0];
	for(int i=1;i< n;i++)for(int j=1;j<=m;j++)
	cin>>tli[i][j][1][0],ans=ans+tli[i][j][1][0];
	for(int i=1;i<=n;i++)for(int j=1;j< m;j++)
	cin>>twen[i][j][0][1],ans=ans+twen[i][j][0][1];
	for(int i=1;i<=n;i++)for(int j=1;j< m;j++)
	cin>>tli[i][j][0][1],ans=ans+tli[i][j][0][1];
	S=(n+2)*(m+2)*6+1;T=S+1;
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
		Add(S,ID(i,j,0),wen[i][j]);
		Add(ID(i,j,0),S,0);
		Add(ID(i,j,0),T,li[i][j]);
		Add(T,ID(i,j,0),0);
	}
	for(int i=1;i<n;i++)for(int j=1;j<=m;j++) {
		Add(S,ID(i,j,1),twen[i][j][1][0]);
		Add(ID(i,j,1),S,0);
		Add(ID(i,j,1),ID(i,j,0),inf);
		Add(ID(i,j,0),ID(i,j,1),0);
		Add(ID(i,j,1),ID(i+1,j,0),inf);
		Add(ID(i+1,j,0),ID(i,j,1),0);
		
		Add(ID(i,j,2),T,tli[i][j][1][0]);
		Add(T,ID(i,j,2),0);
		Add(ID(i,j,0),ID(i,j,2),inf);
		Add(ID(i,j,2),ID(i,j,0),0);
		Add(ID(i+1,j,0),ID(i,j,2),inf);
		Add(ID(i,j,2),ID(i+1,j,0),0);
	}
	for(int i=1;i<=n;i++)for(int j=1;j<m;j++) {
		Add(S,ID(i,j,3),twen[i][j][0][1]);
		Add(ID(i,j,3),S,0);
		Add(ID(i,j,3),ID(i,j,0),inf);
		Add(ID(i,j,0),ID(i,j,3),0);
		Add(ID(i,j,3),ID(i,j+1,0),inf);
		Add(ID(i,j+1,0),ID(i,j,3),0);
		
		Add(ID(i,j,4),T,tli[i][j][0][1]);
		Add(T,ID(i,j,4),0);
		Add(ID(i,j,0),ID(i,j,4),inf);
		Add(ID(i,j,4),ID(i,j,0),0);
		Add(ID(i,j+1,0),ID(i,j,4),inf);
		Add(ID(i,j,4),ID(i,j+1,0),0);
	}
	while(bfs()) ans-=dfs(S,inf);
	cout<<ans<<'\n';
	return 0;
}


#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=1e17+7;
int n,m,wen[105][105],li[105][105];
int twen[105][105][2][2],tli[105][105][2][2];
int ans,sum,S,T,head[(int)2e6+100],cur[(int)2e6+100];
int tot=1,dis[(int)2e6+100];
struct edge{int to,nex,w;}e[(int)2e6+100];
inline int ID(int x,int y,int tag)
{return (x-1)*(m+2)+y+1+(n+2)*(m+2)*tag;}
void Add(int u,int v,int w)
{e[++tot]={v,head[u],w};head[u]=tot;
 e[++tot]={u,head[v],0};head[v]=tot;}
bool bfs() {
	queue<int>q;
	for(int i=1;i<=T;i++) dis[i]=0,cur[i]=head[i];
	dis[S]=1;q.push(S);
	while(q.size()) {
		int u=q.front();q.pop();
		for(int i=head[u];i;i=e[i].nex) {
			int v=e[i].to;
			if(e[i].w>0&&dis[v]==0) {
				dis[v]=dis[u]+1;
				q.push(v);
			}
		}
	}
	return dis[T]>0;
}
int dfs(int u,int flow) {
	if(u==T||flow==0) return flow;
	int f=0;
	for(int i=cur[u];i&&flow;i=e[i].nex) {
		int v=e[i].to;cur[u]=i;
		if(dis[v]==dis[u]+1&&e[i].w>0) {
			int op=dfs(v,min(e[i].w,flow));
			if(op==0) dis[v]=0;
			f+=op;flow-=op;
			e[i].w-=op;e[i^1].w+=op;
		}
	}
	return f;
}
signed main() {
	cin>>n>>m;
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++) 
	cin>>wen[i][j],ans=ans+wen[i][j];
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)
	cin>>li[i][j],ans=ans+li[i][j];
	for(int i=1;i< n;i++)for(int j=1;j<=m;j++)
	cin>>twen[i][j][1][0],ans=ans+twen[i][j][1][0];
	for(int i=1;i< n;i++)for(int j=1;j<=m;j++)
	cin>>tli[i][j][1][0],ans=ans+tli[i][j][1][0];
	for(int i=1;i<=n;i++)for(int j=1;j< m;j++)
	cin>>twen[i][j][0][1],ans=ans+twen[i][j][0][1];
	for(int i=1;i<=n;i++)for(int j=1;j< m;j++)
	cin>>tli[i][j][0][1],ans=ans+tli[i][j][0][1];
	S=(n+2)*(m+2)*6+1;T=S+1;
	for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
		Add(S,ID(i,j,0),wen[i][j]);
		
		Add(ID(i,j,1),T,li[i][j]);
		
		Add(ID(i,j,0),ID(i,j,1),inf);
		
		Add(ID(i,j,0),ID(i,j,3),inf);
		
		Add(ID(i,j,0),ID(i,j,5),inf);
		
		Add(ID(i,j,0),ID(i-1,j,5),inf);
		
		Add(ID(i,j,0),ID(i,j-1,3),inf);
		
		Add(ID(i,j,2),ID(i,j,1),inf);
		
		Add(ID(i,j,4),ID(i,j,1),inf);
		
		Add(ID(i,j-1,2),ID(i,j,1),inf);
		
		Add(ID(i-1,j,4),ID(i,j,1),inf);
		//同文连同理 
		Add(ID(i,j,2),ID(i,j,3),inf);
		
		Add(ID(i,j,2),ID(i,j,5),inf);
		
		Add(ID(i,j,2),ID(i-1,j,5),inf);
		
		Add(ID(i,j,2),ID(i,j-1,3),inf);
		
		Add(ID(i,j,4),ID(i,j,3),inf);
		
		Add(ID(i,j,4),ID(i,j,5),inf);
		
		Add(ID(i,j,4),ID(i-1,j,5),inf);
		
		Add(ID(i,j,4),ID(i,j-1,3),inf);
		
		Add(ID(i-1,j,4),ID(i,j,3),inf);
		
		Add(ID(i-1,j,4),ID(i,j,5),inf);
		
		Add(ID(i-1,j,4),ID(i-1,j,5),inf);
		
		Add(ID(i-1,j,4),ID(i,j-1,3),inf);
		
		Add(ID(i,j-1,2),ID(i,j,3),inf);
		
		Add(ID(i,j-1,2),ID(i,j,5),inf);
		
		Add(ID(i,j-1,2),ID(i-1,j,5),inf);
		
		Add(ID(i,j-1,2),ID(i,j-1,3),inf);
	}
	
	for(int i=1;i<n;i++)for(int j=1;j<=m;j++) {
		Add(S,ID(i,j,4),twen[i][j][1][0]);
		Add(ID(i,j,5),T,tli[i][j][1][0]); 
	}
	for(int i=1;i<=n;i++)for(int j=1;j<m;j++) {
		Add(S,ID(i,j,2),twen[i][j][0][1]);
		Add(ID(i,j,3),T,tli[i][j][0][1]);
	}
	
	while(bfs()) ans-=dfs(S,inf);
	cout<<ans<<'\n';
	return 0;
}

P2046 [NOI2010] 海拔

题目描述

看原题(不儿哥们,链接都在这了)

点击查看代码 首先大胆猜结论:一定存在一种最优方案使得每个点的海拔都为 $0/1$

证明:
如果存在海拔为负的点,那么我们让它们都变成 \(0\) 的话,这些点到其它点的花费会减小的同时,这些点内部的花费也变成了零,所以每个点的海拔一定非负。同理,每个点的海拔也不会超过 \(1\)

再通过反证法证明海拔在取到 \(0/1\) 时最小,假设海拔不都是 \(0/1\) 那对于任意一个海拔不是 \(0/1\) 的联通块 \(i\),它一定不与右下角联通。

  • 如果它与左上角联通,我们找到它周围的海拔最低的联通块 \(j\)(设它的海拔为 \(h\)),则与上文同理,有 \(0 \le h_i \le h_j\) ,而它与周围块的花费为一次函数关系,所以最小值一定在端点处取得

  • 如果它与左上角不连通,我们让 \(h_i\) 变成 \(h_j\) 一定更优

证毕。

但正常建图的话无法通过 \(2.5e5\) 的数据,我们需要用到对偶图来优化。

对于平面图(即可以画在平面上,边的交点只出现在顶点处的图),它的对偶图使将原图中的每一的面(包括有界面和无界面)视为一个点,对于每一条原图上的边 \(e\)

  • 如果 \(e\) 将两个面 $ f_1^{*}$ 和 \(f_2^{*}\) 隔开了,就在对偶图上在 \(f_1^{*}\)\(f_2^{*}\) 之间建一条 \(e.w\) 的边

  • 如果 \(e\) 的两边为同一个面,就连一个自环

对偶图有以下性质:

  • 对偶图上的一个环对应原图上的一个割(对于一个图,一种删边方式,使得原图不连通,就是原图的一个割)

  • 原图的最大流最小割就是对偶图的最短路

所以我们考虑对原图建出对偶图。

如何建对偶图呢

先给源点和汇点连一条虚边,将一个无界面分为两个面,让其中一个面代表源点,另一个面代表汇点,根据定义连边即可。

在对偶图中不能建双向边,每条边可以由原图上的每条边 顺/逆时针旋转得到,至于是顺时针还是逆时针,与源点、汇点代表的面有关。

这样就将时间复杂度变成了 \(O(n logm)\)

#include<bits/stdc++.h>
#define mk make_pair
#define int long long
using namespace std;
const int N=5e6+100,inf=1e16+7;
int n,S,T,tot=1,head[N],ans,dis[N];
bool vis[N];
struct edge{int to,nex;long long w;}e[N<<1];
inline void Add(int u,int v,long long w)
{e[++tot]={v,head[u],w};head[u]=tot;}
inline int ID(int x,int y)
{return (x-1)*(n+2)+y;}
void Dij() {
	priority_queue<pair<int,int>,vector<pair<int,int> > ,greater<pair<int,int> > >q;
	for(int i=1;i<=T;i++) dis[i]=inf;
	dis[S]=0;q.push(mk(dis[S],S));
	while(q.size()) {
		int u=q.top().second;q.pop();
		if(vis[u])continue;vis[u]=1;
		for(int i=head[u];i;i=e[i].nex) {
			int v=e[i].to;
			if(dis[v]>dis[u]+e[i].w) {
				dis[v]=dis[u]+e[i].w;
				q.push(mk(dis[v],v));
			}
		}
	}
	printf("%lld\n",dis[T]);
	return ;
}
signed main() {
	scanf("%lld",&n);S=(n+3)*(n+3)+1;T=S+1;
	for(int i=1;i<=n+1;i++)for(int j=1;j<=n;j++) {
		int op;scanf("%lld",&op);
		if(i==1) {
			Add(ID(i,j),T,op);
//			Add(T,ID(i,j),op);
			continue;
		}
		if(i==n+1) {
//			Add(ID(i-1,j),S,op);
			Add(S,ID(i-1,j),op);
			continue;
		}
		Add(ID(i,j),ID(i-1,j),op);
//		Add(ID(i-1,j),ID(i,j),op);
	}
	for(int i=1;i<=n;i++)for(int j=1;j<=n+1;j++) {
		int op;scanf("%lld",&op);
		if(j==1) {
			Add(S,ID(i,j),op);
//			Add(ID(i,j),S,op);
			continue;
		}
		if(j==n+1) {
			Add(ID(i,j-1),T,op);
//			Add(T,ID(i,j-1),op);
			continue;
		}
//		Add(ID(i,j),ID(i,j-1),op);
		Add(ID(i,j-1),ID(i,j),op);
	}
	for(int i=1;i<=n+1;i++)for(int j=2;j<=n+1;j++) {
		int op;scanf("%lld",&op);
		if(i==1) {
//			Add(ID(i,j-1),T,op);
			Add(T,ID(i,j-1),op);
			continue;
		}
		if(i==n+1) {
			Add(ID(i-1,j-1),S,op);
//			Add(S,ID(i-1,j-1),op);
			continue;
		}
//		Add(ID(i,j-1),ID(i-1,j-1),op);
		Add(ID(i-1,j-1),ID(i,j-1),op);
	}
	for(int i=2;i<=n+1;i++)for(int j=1;j<=n+1;j++) {
		int op;scanf("%lld",&op);
		if(j==1) {
//			Add(S,ID(i-1,j),op);
			Add(ID(i-1,j),S,op);
			continue;
		}
		if(j==n+1) {
//			Add(ID(i-1,j-1),T,op);
			Add(T,ID(i-1,j-1),op);
			continue;
		}
		Add(ID(i-1,j),ID(i-1,j-1),op);
//		Add(ID(i-1,j-1),ID(i-1,j),op);
	}
	Dij();
	return 0;
}

P3227 [HNOI2013] 切糕

题目描述

\(n*m\) 个纵轴,每个纵轴上选择一个点,相邻纵轴上选择的位置之差不能大与给定的参数 \(D\) ,最小化代价之和

点击查看代码 有两个限制,一个是限制单个纵轴上只能选一个,一个是限制相邻的纵轴之间选数的限制。

对于第一个限制,每个点的选择与其它点矛盾,可以将每一条纵轴连在一起,第 \(j\) 个向第 \(j+1\) 连一条 \(a_j\) 的边,被割去就代表被选上了,每一条链只能割一条边,这符合题目中的每一个纵轴只能选一个的要求

对于第二个限制,它就等价于要求每一条链上的第 \(j\) 个与和它相邻的链上的第 \(j-D-1\)、第 \(j-D+2\)...还有第 \(j+D+1\)....不能同时选择,那就要保证在第 \(j\) 个和第 \(j+D+1\) 同时被割去时,源汇点仍然联通

分开考虑即可


#include<bits/stdc++.h>
using namespace std;
const int inf=1e9,N=4e6+100;
const int X[]={1,-1,0,0},Y[]={0,0,1,-1};
int P,Q,R,D,S,T,ans,dis[N],cur[N],head[N],tot=1;
int a[45][45][45];
inline int ID(int x,int y,int z) 
{return (z-1)*P*Q+(x-1)*Q+y;}
struct edge{int to,nex,w;}e[N<<1];
inline void Add(int u,int v,int w)
{e[++tot]={v,head[u],w};head[u]=tot;
 e[++tot]={u,head[v],0};head[v]=tot;}
inline bool bfs() {
	queue<int>q;
	for(int i=1;i<=T;i++) dis[i]=0,cur[i]=head[i];
	dis[S]=1;q.push(S);
	while(q.size()) {
		int u=q.front();q.pop();
		for(int i=head[u];i;i=e[i].nex) {
			int v=e[i].to;
			if(e[i].w>0&&dis[v]==0) {
				dis[v]=dis[u]+1;
				q.push(v);
			}
		}
	}
	return dis[T]>0;
}
inline int dfs(int u,int flow) {
	if(u==T||flow==0) return flow;
	int f=0;
	for(int i=cur[u];i&&flow;i=e[i].nex) {
		int v=e[i].to;cur[u]=i;
		if(dis[v]==dis[u]+1&&e[i].w>0) {
			int op=dfs(v,min(flow,e[i].w));
			if(op==0) dis[v]=0;
			flow-=op;f+=op;
			e[i].w-=op;e[i^1].w+=op;
		}
	}
	return f;
}
signed main() {
	scanf("%d%d%d%d",&P,&Q,&R,&D);
	S=P*Q*(R+1)*2+50;T=S+1;
	for(int z=1;z<=R;z++) for(int x=1;x<=P;x++) 
	for(int y=1;y<=Q;y++) scanf("%d",&a[x][y][z]);
	for(int x=1;x<=P;x++) for(int y=1;y<=Q;y++) {
		Add(S,ID(x,y,1),inf);
		for(int z=1;z<=R;z++) 
		Add(ID(x,y,z),ID(x,y,z+1),a[x][y][z]);
		Add(ID(x,y,R+1),T,inf);
	}
	for(int x=1;x<=P;x++) for(int y=1;y<=Q;y++) {
		for(int z=1;z<=R;z++) {
			for(int op=0;op<4;op++) {
				int tx=x+X[op],ty=y+Y[op];
				if(tx<1||tx>P||ty<1||ty>Q) continue;
				Add(ID(x,y,z),ID(tx,ty,max(z-D,1)),inf);
			}
		}
	}
	while(bfs()) ans+=dfs(S,inf);
	printf("%d\n",ans);
	return 0;
}

P6054 [RC-02] 开门大吉

题目描述

自己去看luogu

点击查看代码 等我口胡

#include<bits/stdc++.h>
using namespace std;
const double inf=1e9,eps=1e-7;
int Tim,n,m,p,y,c[105],S,T,dis[(int)2e6+100];
double f[85][85][85],P[85],Cost[85][85],ans;
int tot=1,head[(int)2e6+100],cur[(int)2e6+100];
struct edge{int to,nex;double w;}e[(int)4e6+100];
inline int ID(int x,int y)
{return (x-1)*(m+2)+y;}
void Add(int u,int v,double w)
{e[++tot]={v,head[u],w};head[u]=tot;
e[++tot]={u,head[v],0};head[v]=tot;}
bool bfs() {
	for(int i=1;i<=T;i++) dis[i]=0,cur[i]=head[i];
	queue<int>q;dis[S]=1;q.push(S);
	while(q.size()) {
		int u=q.front();q.pop();
		for(int i=head[u];i;i=e[i].nex) {
			int v=e[i].to;
			if(dis[v]==0&&(e[i].w>eps)) {
				dis[v]=dis[u]+1;
				q.push(v);
			}
		}
	}
	return dis[T]>0;
}
double dfs(int u,double flow) {
	if(u==T||fabs(flow)<eps) return flow;
	double f=0;
	for(int i=cur[u];i&&(flow>eps);i=e[i].nex) {
		int v=e[i].to;cur[u]=i;
		if(dis[v]==dis[u]+1&&e[i].w>eps) {
			double op=dfs(v,min(flow,e[i].w));
			if(fabs(op)<eps) dis[v]=0;
			f+=op;flow-=op;
			e[i].w-=op;e[i^1].w+=op;
		}
	}
	return f;
}
signed main(){
	cin>>Tim;
	while(Tim--) {
		cin>>n>>m>>p>>y;
		for(int i=1;i<=p;i++) cin>>c[i];
		for(int i=2;i<=p;i++) c[i]+=c[i-1];
		for(int j=1;j<=m;j++) for(int i=1;i<=n;i++) 
		for(int k=1;k<=p;k++) cin>>f[i][j][k];
		for(int i=1;i<=n;i++) {
			for(int j=1;j<=m;j++){
				P[1]=f[i][j][1];
				for(int k=2;k<=p;k++) P[k]=P[k-1]*f[i][j][k];
				double op=0;
				for(int k=2;k<=p;k++) 
				op=op+P[k-1]*(1-f[i][j][k])*c[k-1];
				op=op+P[p]*c[p];
				Cost[i][j]=op;
			}
		}
		S=(m+2)*(n+2)+1;T=S+1;
		for(int i=1;i<=n;i++) {
			Add(S,ID(i,1),inf);
			for(int j=1;j<=m;j++)Add(ID(i,j),ID(i,j+1),Cost[i][j]);
			Add(ID(i,m+1),T,inf);
		}
		for(int q=1;q<=y;q++) {
			int i,j,k;cin>>i>>j>>k;
			for(int idj=1;idj<=m;idj++) {
				int idi=idj+k;
				if(idi>=1&&idi<=m) {
					Add(ID(j,idj),ID(i,idi),inf);
					continue;
				}
				if(idi<1) {continue;}
				if(idi>m) Add(ID(j,idj),ID(j,idj+1),inf);
			}
		}
		ans=0;
		while(bfs()&&ans<=inf) ans+=dfs(S,inf);
		if(ans>inf) printf("-1\n");
		else printf("%.7lf\n",ans);
		tot=1;for(int i=1;i<=T;i++) head[i]=cur[i]=0;
	}
	return 0;
}

丢一道对偶图练习题 P4001 [ICPC-Beijing 2006] 狼抓兔子

犹小之影

posted on 2025-08-01 21:37  Pearblossom  阅读(30)  评论(3)    收藏  举报

导航