今天讲一道比较难点的题,到时考场上没做出来,不过现在已经会了
目录
题目:蛋糕划分
小度准备切一个蛋糕。这个蛋糕的大小为 N∗N,蛋糕每个部分的重量并不均匀。
小度一共可以切 K刀,每一刀都是垂直或者水平的,现在小度想知道在切了 K刀之后,最重的一块蛋糕最轻的重量是多少。
思路:
首先这个暴力dfs不行,横刀14下,竖刀14下,共2^28*14^2会超时的
其实对最大质量的蛋糕块二分答案即可,难在对答案判断:首先我们对所有的横刀状态模拟,用一个数的(n-1)位的表示所有状态即可,然后根据此时横刀状态对竖刀进行决策,方法是:
从第一列开始,计算每列区间和,若此时横刀区间和加上新的一列区间后值大于答案了,那就要在此列左边切竖刀,然后在此竖刀右面重新计算新一列区间,
不断重复。其实就是每个横刀区间加上新一列区间后都不要大于答案,如果大于我们就要这里要切竖刀了。最后统计竖刀和横刀数,返回结果即可(另外,为了加速处理,还要用上二维前缀和)
#include <bits/stdc++.h> //蛋糕划分:蛋糕大小N*N,每个部分质量不均,共切k刀(横竖均可),使最重的一块蛋糕的质量最轻是多少。
using namespace std; //2≤N≤15,1≤K≤2N−2 ,每个位置质量W小于1000 和洛谷P1182 数列分段一样思路
int n,k,fal,max_;//fal是答案出错标记,方便快速结束循环
int a[20][20],sum[20][20],col[20],cnt[20],temp[20];;//col存放竖刀的位置,cnt是横切后每个区间和,temp是新一列区间和
int get_sum(int x1,int y1,int x2,int y2){//求上下左右区间和
return sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1];
}
bool check(int m){
if(m<max_)return false;//特判
//第一个循环是对每种横刀状态进行遍历(每一个i都代表一种横刀状态)
for(int i=0;i<(1<<(n-1));i++){
fal=0;//更新此种横刀状态为正确,内部循环发现是因横刀出错时就一头冲出来
memset(col,0,sizeof(col));
memset(cnt,0,sizeof(cnt));
memset(temp,0,sizeof(temp));
vector<int> v;
int count=0,ans=0, tmp=i;
while(tmp){
count++;
if(tmp&1){//获取i中1的位置,存入这种状态下的每个横刀位置
v.push_back(count);//v用来存横刀位置,若v里面为1,3,4,加入n后,表示横刀区间1-1,2-3,4-4,5-n
ans++;
}
tmp/=2;
}
if(ans>k)continue;//特判
v.push_back(n);
int p=0,top=1,down;//top是该区间最上面元素位置,down是此横刀位置,p为当前的第几个横刀区间(
//第二个循环是切竖刀的,在每个竖位置遍历列区间,一旦某个横刀区间加上这个列超过答案就说明可以切竖刀了
for(int j=1;j<=n;j++){
p=0,top=1;//每切一次竖刀就重置一次
//第三个循环是遍历横刀区间的(我们要自上而下遍历每个区间)
for(int x:v){//用x来访问v中的所有元素,取出每个横刀位置
down=x;
int now=get_sum(top,j,down,j);//求列区间和(列宽为1)
if(now>m){//如果仅列区间已经大于答案就说明是横刀状态失误了,fal标记一下,直接冲到最外面
fal=1; break;
}
//求新的横刀区间(原横刀区间加列区间)
cnt[++p]+=now;//将上个区间加上这个区间
if(col[j-1]){
cnt[p]=now;//上个位置被切过了,就说明我们不应该加的
}
temp[p]=now;//temp存每个列区间和
//判断该横刀区间,决策是否切竖刀
if(cnt[p]>m){//该区间和大于答案,说明要切竖着在j-1的位置一刀,cnt再从第j列开始计算和(你只有犯错的时候才知道自己犯错了)
col[j-1]=1;//更新竖切的位置和区间和
cnt[p]=now;
for(int ii=1;ii<p;ii++)cnt[ii]=temp[ii];//之前的区间也要因此而更新了,用上temp了
}
top=down+1;//top是下个区间最上面位置
}
if(fal)break;
}
if(fal)continue;
for(int j=1;j<=n-1;j++)
if(col[j])ans++;//col存放的竖切刀
if(ans<=k)return true;
}
return false;
}
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++)//数据从1,1开始存,后面注意这点
for(int j=1;j<=n;j++){
cin>>a[i][j];
max_=max(max_,a[i][j]);
sum[i][j]=a[i][j]+sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
}
int l=0,r=2000*15*15;
while(l<=r){
int m=(l+r)>>1;
if(check(m)) r=m-1;//刀数太少,说明应该再切小一点
else l=m+1;
}
cout<<l<<'\n';
return 0;
}
今天的确实有点难,嗯