BZOJ3144【HNOI2013】切糕

本文介绍了一种使用网络流算法解决特定切糕问题的方法。通过对问题进行建模,利用最大流最小割原理,实现了在满足光滑性要求的基础上找到最小总不和谐值的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

这里写图片描述

Input

第一行是三个正整数P,Q,R,表示切糕的长P、 宽Q、高R。第二行有一个非负整数D,表示光滑性要求。接下来是R个P行Q列的矩阵,第z个 矩阵的第x行第y列是v(x,y,z) (1≤x≤P, 1≤y≤Q, 1≤z≤R)。
100%的数据满足P,Q,R≤40,0≤D≤R,且给出的所有的不和谐值不超过1000。

Output

仅包含一个整数,表示在合法基础上最小的总不和谐值。

Sample Input

2 2 2

1

6 1

6 1

2 6

2 6

Sample Output

6
HINT

最佳切面的f为f(1,1)=f(2,1)=2,f(1,2)=f(2,2)=1


我们建一个r+1层的,每层都是p*q的图,同时建两个空点S和T,表示源点和汇点。从源点S连到第一层的所有边都赋值为2e8,第r+1层到汇点T的边都赋值为2e8。而点(x,y,z)向点(x,y,z+1)连一条权值为v(x,y)的边,这样我们就把这个题目构筑成一个网络流图了。

那我们先想:如果没有题目里对切割点距离的要求,那我们直接求出这张图的最大流即可。(因为该题很明显是求这张图的最小割,而根据最大流最小割定理,最大流=最小割)
那我们再想:当有距离限制时,做法也是一样的,我们依旧只需要求出这张图的最小割即可,但是在求之前,我们要先连一些特殊的边。
特殊的边:对于点(x,y,z),我们要连上(x,y,z)和(x1,y1,z+d),并赋值为2e8(其中x1,y1表示与(x,y)临近的点,具体来说就是(x,y-1)(x,y+1)(x-1,y)(x+1,y)这四个点)
那么我们为什么要连这些边呢?这里做个证明:

这里写图片描述

如图所示,这是一个d为1的图,绿色的边在红色的边下面两格,所以在这张图里不可以同时选红色和绿色。我们根据最小割的定义,最小割必须把这张图分割成两个点集。如图我们连过边后,若选用红色和绿色,我们发现割去后整张图依旧在一个点集里,所以不可行。而如果我们选择红色和紫色的边,会发现这两条边将会将整张图割为两个点集,满足最小割的定义。红边和紫边的间隔只有一格,也满足题目里的距离限制。
我们通过这个小tips就可以解决这道题了。

Code:

#include<bits/stdc++.h>
using namespace std;

int read(){
    char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
} 

const int maxl=45,maxn=75000,maxm=maxn<<4,inf=2e8,mov[4][2]={{0,1},{1,0},{0,-1},{-1,0}};
int id[maxl][maxl][maxl],head[maxn],nxt[maxm],Q[maxm],dis[maxn];
int q,p,r,d,ans,T,S,cnt,num,h,t,tot,xx;

struct node{
    int to,val;
}L[maxm];

void add(int x,int y,int val){
    L[cnt]=(node){y,val};
    nxt[cnt]=head[x];head[x]=cnt;cnt++;
    L[cnt]=(node){x,0};
    nxt[cnt]=head[y];head[y]=cnt;cnt++;
}

int BFS(){
    memset(dis,0,sizeof(dis));
    dis[S]=1;h=0;t=1;Q[t]=S;
    while(h<t){
        int front=Q[++h];
        for(int i=head[front];i!=-1;i=nxt[i]){
            int to=L[i].to;
            if(!dis[to]&&L[i].val){
                dis[to]=dis[front]+1;
                Q[++t]=to;
            }
        }
    }
    return dis[T];
}

int DFS(int now,int x){
    if(now==T) return x;
    int res=0;
    for(int i=head[now];i!=-1&&x;i=nxt[i]){
        int to=L[i].to;
        if(dis[to]==dis[now]+1&&L[i].val){
            int fd=DFS(to,min(x,L[i].val));
            res+=fd;x-=fd;
            L[i].val-=fd;L[i^1].val+=fd;
        }
    }
    if(!res) dis[now]=0;
    return res;
}

int main()
{
    memset(head,-1,sizeof(head));
    p=read();q=read();r=read();d=read();tot=0;
    for(int i=1;i<=r+1;i++)  
     for(int j=1;j<=p;j++)  
      for(int k=1;k<=q;k++) id[i][j][k]=++tot; 
    S=0;T=++tot;
    for(int i=1;i<=r;i++)
     for(int j=1;j<=p;j++)
      for(int k=1;k<=q;k++) 
        xx=read(),add(id[i][j][k],id[i+1][j][k],xx); 
    for(int i=1;i<=p;i++)
     for(int j=1;j<=q;j++) add(S,id[1][i][j],inf),add(id[r+1][i][j],T,inf);
    #define nx (j+mov[mv][0])  
    #define ny (k+mov[mv][1])  
    for(int i=d+1;i<=r+1;i++)  
     for(int j=1;j<=p;j++)  
      for(int k=1;k<=q;k++)  
       for(int mv=0;mv<4;mv++)  
        if(id[i][nx][ny]) add(id[i][j][k],id[i-d][nx][ny],inf);  
    #undef nx  
    #undef ny 
    while(BFS()){
        ans+=DFS(S,2e9);
    }
    printf("%d\n",ans); 
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值