230909 NOIP 模拟赛 T1 cake 题解

发布时间 2023-09-11 12:45:45作者: MrcFrst_LRY

原题

题意

有一块 \(n\times m\) \((1\le n,m\le 14)\) 的蛋糕,每个位置上有一个权值 \(a_{i,j}\) \((1\le a_{i,j}\le 1000)\),现在你要把它切开。每次你可以平行与某一边界把蛋糕切开,所以共有 \(n-1\) 个可以竖着切的位置,以及 \(m-1\) 个可以横着切的位置。

对于每一组 \(i,j\) \((0\le i\lt n,0\le j\lt m)\) ,你需要求得某种切割方案使得切完后每一块蛋糕的权值之和的最小值最大,输出这些最大值。


首先来个二维前缀和,然后我们来考虑具体做法。

数据范围一眼状压。那么久先考虑一个最暴力的状压做法,就是暴力枚举横着切的状态和竖着切的状态,然后计算答案。

光是枚举就 \(O(2^{n+m}nm)\) 了,不说了。

然后考虑到是对每一组 \(i,j\) 求解,所以考虑舍弃掉每次都重新算,就用 \(dp\) 的方式转移,然后我们来考虑把上面那东西变成 \(dp\) 的形式。

然后你就会发现把两维都状压真的很逆天,因为你在转移的时候计算贡献要用到的信息其实只有,就是,假如我们先只考虑一维,每次你切下一刀,转移的时候其实只关心上一刀切在哪里,然后中间多出来的那一堆和这一刀后面那一堆是需要计算的贡献,所以就是说你只需要知道上一刀切在哪里就可以了。

那么就有一个经典的 \(dp\):设 \(dp_{i,j}\) 表示第 \(i\) 刀切在位置 \(j\) 的答案。

然后就可以发现如上述计算贡献的时候,你还需要知道另一维是怎么分割的,所以可以加一维状压。

总的就是 \(dp_{i,j,k}\) 表示当横着的分割状态为 \(i\) 的时候,竖着的第 \(j\) 刀切在位置 \(k\) 的答案。

然后呢可以发现这题的的数据范围的话,对于状态 \(i\),即使不进行转移,也就是一个一个单独地算,也是完全能过的,所以直接枚举 \(i\) 就好了,不用把它加进 \(dp\)

\(dp\) 部分转移的时候枚举上一刀切在哪里就好了,复杂度是 \(O(n^4)\)。稍微预处理一下就是 \(O(n^3)\)。但是枚举的时候很多地方跑不满,所以吸氧之后两种复杂度都跑得飞快(

总的时间复杂度就是 \(O(2^nn^3)\)


总结

这题的过程大概就是,先考虑暴力,然后注意到题目的问题是要对每一组 \(i,j\) 求解,所以考虑通过转移的方式快速计算,而不是一个一个地单独计算,那么我们就考虑每次切一刀会对答案造成怎样的影响,然后就发现这个时候又要考虑横着的状态又要考虑竖着的状态,很难受啊,看一眼数据范围,那就状压吧,但是横着和竖着都状压很窒息啊,然后就思考每次切一刀然后计算贡献的时候需要的信息到底有哪些,然后就能发现当竖着的状态钦定好以后,横着切一刀的话只需要知道上一刀在哪里,然后就有了那个 \(dp\)。感觉思路是比较好的。

\(Code:\)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define il inline
#define re register
const int N=15;
int n,m,a[17][17],s[17][17],dp[N][N],U;
int ans[17][17],suf[N];
#define pb push_back
vector<int>v;
il int read(){
    re int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*f;
}
il int Sum(int x1,int y1,int x2,int y2){
    return s[x2][y2]-s[x2][y1-1]-s[x1-1][y2]+s[x1-1][y1-1];
}
il bool ex(int i,int j){
    return ((i>>(j-1))&1);
}
int main(){
//	freopen("cake3.in","r",stdin);
//	freopen("cake3.out","w",stdout);
    n=read(),m=read();
    U=1<<(n-1);
    for(re int i=1;i<=n;i++)
        for(re int j=1;j<=m;j++)a[i][j]=read();
    for(re int i=1;i<=n;i++)
        for(re int j=1;j<=m;j++)
            s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
    for(re int i=0;i<U;i++){
        v.clear();
        v.pb(0);
        for(re int j=1;j<n;j++)
            if(ex(i,j))v.pb(j);
        v.pb(n);
        int siz=v.size();
        for(re int j=0;j<=m;j++)
            for(re int k=0;k<=m;k++)dp[j][k]=0;
        for(re int j=0;j<m;j++){
            int res=1e9;
            for(re int k=1;k<siz;k++)
                res=min(res,Sum(v[k-1]+1,j+1,v[k],m));
            suf[j]=res;
        }
        dp[0][0]=suf[0];
        int cnti=__builtin_popcount(i);
        ans[cnti][0]=max(ans[cnti][0],dp[0][0]);
        for(re int now=1;now<m;now++)
        	for(re int pst=0;pst<now;pst++){
                int res=1e9;
                for(re int k=1;k<siz;k++)res=min(res,Sum(v[k-1]+1,pst+1,v[k],now));
                for(re int cnt=1;cnt<=now;cnt++){
                	dp[now][cnt]=max(dp[now][cnt],min(dp[pst][cnt-1],min(res,suf[now])));
                	ans[cnti][cnt]=max(ans[cnti][cnt],dp[now][cnt]);
            	}
            }
    }
    for(re int i=0;i<n;i++,putchar('\n'))
        for(re int j=0;j<m;j++)printf("%d ",ans[i][j]);
    return 0;
}