【每日一题】2023百度之星-蛋糕划分(二分,前缀和,模拟) 保姆级教程

今天讲一道比较难点的题,到时考场上没做出来,不过现在已经会了

        

目录

题目:蛋糕划分

​编辑

思路:


题目:蛋糕划分

小度准备切一个蛋糕。这个蛋糕的大小为 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;
}

今天的确实有点难,嗯

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值