[HNOI2013]切糕

这篇博客探讨了一种利用网络流算法解决最大最小割问题的方法。具体来说,作者详细介绍了如何构建图,包括将同一列的点连接起来,并在相邻列之间设置合适的限制,确保满足距离不超过ddd的条件。通过建立网络流模型并运行求解,可以找到总成本的最小值。文章以P×Q×R的矩阵为例,给出了具体的代码实现,最后得出复杂度为O(P^3Q^3R^3)。
摘要由CSDN通过智能技术生成

切糕

题解

以前做过的题,现在却只能想到"网络流"三个字,怎么建图都不会了。

应该很容易想到,通过最大流最小割来求出总的最小值。
首先我们对于同一列,我们至少要割掉其中一个点,我们可以将这 R R R个点都连成一列,从 ( i , j , k ) − > ( i , j , k + 1 ) (i,j,k)->(i,j,k+1) (i,j,k)>(i,j,k+1)连一条流量为 v a l i , j , k val_{i,j,k} vali,j,k的边,割掉这条边就表示在这一列选择割点 ( i , j , k ) (i,j,k) (i,j,k)
这样形成的一条链,接在 S S S T T T间,显然,在这一条链,肯定会有一条边被割掉,也只会割一条。
我们继续考虑对于相邻列,它们之间是不能选择距离超过 d d d的点的。

如果我们在某一列上割了点 x x x,那么另一列中就得从 [ x − d , x + d ] [x-d,x+d] [xd,x+d]中选一个点割掉,显然直接这样是不大好限制的,我们可以将这个限制转化一下。
转化成我们在一列的 [ 1 , x ] [1,x] [1,x]与另一列的 [ x − d , r ] [x-d,r] [xd,r]中至少要选一个。
否则的话就意味着我们两个点的距离是一定超过 d d d的。
x x x取遍 [ x , r ] [x,r] [x,r],且每一列都有这样的限制时,可以证明,是符合我们之前的条件的。
我们不妨设我们在该列上取了 x x x,在另一列上去了 y y y
如果 y > x + d y>x+d y>x+d,那么就存在 S − > y − > ( y − d > x ) − > T S->y->(y-d>x)->T S>y>(yd>x)>T的一条联通路径,显然是不合法的。
y < x − d y<x-d y<xd时,也同样存在 S − > x − > ( x − d > y ) − > T S->x->(x-d>y)->T S>x>(xd>y)>T的一条联通路径,同样是不合发的。
实际上的连边我们从 ( i , j , k ) − > ( i + d x , j + d y , k − d ) (i,j,k)->(i+dx,j+dy,k-d) (i,j,k)>(i+dx,j+dy,kd)连一条流量为 I N F INF INF的边就行了。
显然,我们建出图后跑一遍网络流求出最小割就行了。

时间复杂度 O ( P 3 Q 3 R 3 ) O\left(P^3Q^3R^3\right) O(P3Q3R3),反正网络流的时间复杂度又不重要

源码

#include<bits/stdc++.h>
using namespace std;
#define MAXN 70005
#define MAXM 400005
#define lowbit(x) (x&-x)
#define reg register
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
typedef long long LL;
typedef unsigned long long uLL; 
typedef long double ld;
typedef pair<int,int> pii;
const int INF=0x3f3f3f3f;
const int mo=1e9+7;
const int inv2=5e8+4;
const int jzm=23333;
const int zero=20000;
const int lim=1000000;
const int orG=3,ivG=334845270;
const double Pi=acos(-1.0);
const double eps=1e-12;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
template<typename _T>
void print(_T x){if(x<0){x=(~x)+1;putchar('-');}if(x>9)print(x/10);putchar(x%10+'0');}
int gcd(int a,int b){return !b?a:gcd(b,a%b);}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*t*a%p;a=1ll*a*a%p;s>>=1;}return t;}
int P,Q,R,D,head[MAXN],tot,val[45][45][45],id[45][45][45],S,T,cnt;
int dx[5]={1,-1,0,0},dy[5]={0,0,1,-1},dis[MAXN],cur[MAXN];
queue<int>q;
struct edge{int to,nxt,flow,op;}e[MAXM<<1];
void addEdge(int u,int v,int f){e[++tot]=(edge){v,head[u],f};head[u]=tot;}
void addedge(int u,int v,int f){
	addEdge(u,v,f);e[tot].op=tot+1;
	addEdge(v,u,0);e[tot].op=tot-1;
}
bool bfs(){
	for(int i=1;i<=cnt;i++)dis[i]=0,cur[i]=head[i];
	while(!q.empty())q.pop();dis[S]=1;q.push(S);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=head[u],v;i;i=e[i].nxt)
			if(e[i].flow&&!dis[v=e[i].to]){
				dis[v]=dis[u]+1,q.push(v);
				if(v==T)return 1;
			}
	}
	return 0;
}
int dfs(int u,int maxf){
	if(u==T)return maxf;int res=0;
	for(int i=cur[u];i;cur[u]=i=e[i].nxt){
		int v=e[i].to;if(dis[v]!=dis[u]+1||!e[i].flow)continue;
		int tmp=dfs(v,min(maxf,e[i].flow));
		e[i].flow-=tmp;e[e[i].op].flow+=tmp;maxf-=tmp;res+=tmp;
		if(!maxf)break;
	}
	return res;
}
int sakura(){int res=0;while(bfs())res+=dfs(S,INF);return res;}
signed main(){
	read(P);read(Q);read(R);read(D);S=++cnt;T=++cnt;
	for(int k=1;k<=R;k++)
		for(int i=1;i<=P;i++)
			for(int j=1;j<=Q;j++)read(val[i][j][k]);
	for(int i=1;i<=P;i++)
		for(int j=1;j<=Q;j++)
			for(int k=0;k<=R;k++)id[i][j][k]=++cnt;
	for(int i=1;i<=P;i++)
		for(int j=1;j<=Q;j++){
			addedge(S,id[i][j][0],INF);
			for(int k=1;k<=R;k++)addedge(id[i][j][k-1],id[i][j][k],val[i][j][k]);
			for(int p=0;p<4;p++){
				int x=i+dx[p],y=j+dy[p];if(x<1||y<1||x>P||y>Q)continue;
				for(int k=D;k<=R;k++)addedge(id[i][j][k],id[x][y][k-D],INF);
			}
			addedge(id[i][j][R],T,INF);
		}
	printf("%d\n",sakura());
	return 0;  
}

谢谢!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值