刷题集(8)

说明


zjn 有 n 块积木,每块积木的高度都相同,但是底面面积不同,第 ii块积木的底面面积为 wi

现在 zjn 用这些积木来搭一个 k层高的高塔,每一层只用一块积木,而为了高塔的稳定,对于相邻两层的积木来说,下层积木的底面面积必须是上层积木的两倍以上

现在 zjn 想知道,自己最多能搭几个 kk 层的高塔?

输入格式


第一行两个正整数 n,k,分别表示积木的块数,以及高塔的层数。

第二行 n 个正整数 w,分别表示每块积木的底面面积。


对于 30%的数据满足:n,k≤7

对于 100%的数据满足:2≤n≤2∗105,2≤k≤30,k≤n,1≤wi≤231

输出格式


输出一行一个整数,表示最多可搭建的高塔的个数。

样例

输入数据 1

9 3
6 1 11 4 8 7 2 18 3

输出数据 1

2

提示


其中一组方案,两个高塔分别是:{1 2 41 2 4},{3 7 183 7 18}

积木高塔问题思路

问题分析

我们需要用n块积木搭建尽可能多的k层高塔。每层使用一块积木,且相邻两层中,下层积木的底面面积必须至少是上层积木的两倍。目标是最大化高塔的数量。

算法选择

采用​​二分答案​​结合​​贪心匹配​​的策略:

  • ​二分答案​​:因为高塔数量t满足单调性(如果t可行,则小于t的值都可行;如果t不可行,则大于t的值都不可行)。t的可能范围是0到⌊n/k⌋。

  • ​贪心匹配​​:对于每个候选t,检查是否能搭建t个高塔。通过排序积木和贪心选择,确保每次使用最小的满足条件的积木,以最大化塔数。

步骤详解
  1. ​排序积木​​:将积木按底面面积从小到大排序。这便于后续贪心匹配,因为小面积积木更适合作为顶层,大面积积木更适合作为底层。

  2. ​二分查找t​​:

    • 初始化左边界left = 0,右边界right = n // k。

    • 当left ≤ right时:

      • 计算中间值mid = (left + right) // 2。

      • 检查是否能搭建mid个k层高塔(调用检查函数)。

      • 如果可行,则尝试更大的t(left = mid + 1);否则,尝试更小的t(right = mid - 1)。

    • 最终right是最大可行t。

  3. ​检查函数(判断t是否可行)​​:

    • 如果t=0,直接返回true。

    • ​初始化t个塔​​:选择前t个最小的积木作为每个塔的顶层(因为顶层面积小,更容易满足下层面积大的要求)。

    • ​逐层构建​​:对于第2层到第k层:

      • 将t个塔的当前顶层面积(即上一层的面积)排序,以便贪心匹配。

      • 从剩余积木中,为每个塔分配一个最小的满足条件(积木面积 ≥ 2 × 当前顶层面积)的积木。如果找不到足够的积木,则t不可行。

      • 成功分配后,更新每个塔的当前层积木。

    • 如果所有层都分配成功,则t可行。

复杂度分析
  • ​时间复杂度​​:排序积木O(n log n)。二分查找O(log(n/k)),每次检查需要O(k × n)(因为每层需要遍历积木,k很小)。总复杂度O(n log n + k n log(n/k)),在n≤200,000和k≤30时可行。

  • ​空间复杂度​​:O(n)用于存储积木和临时塔信息。

关键点
  • ​贪心选择​​:每次为塔分配最小的满足条件的积木,以保留大面积积木用于后续层或其他塔,从而最大化塔数。

  • ​排序优化​​:积木排序后,贪心匹配可以高效进行,避免重复扫描。

这种思路确保了在大规模数据下高效求解,直接聚焦问题本质。

代码样例

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

const int N=200010;
int n,k;
ll w[N];

bool check(int t)
{
	if(t==0) return true;
	vector<ll> v(t);
	for(int i=0; i<t; i++) v[i]=w[i];
	int j=t;
	for(int l=2; l<=k; l++)
	{
		sort(v.begin(),v.end());
		int cnt=0;
		for(int i=0; i<t; i++)
		{
			while(j<n&&w[j]<2*v[i]) j++;
			if(j<n)
			{
				v[i]=w[j];
				j++;
				cnt++;
			}
			else break;
		}
		if(cnt<t) return false;
	}
	return true;
}

int main()
{
	cin>>n>>k;
	for(int i=0; i<n; i++) cin>>w[i];
	sort(w,w+n);
	int l=0,r=n/k,ans=0;
	while(l<=r)
	{
		int mid=l+(r-l)/2;
		if(check(mid))
		{
			ans=mid;
			l=mid+1;
		}
		else r=mid-1;
	}
	cout<<ans;
	return 0;
}

此代码仅供参考,请勿纯抄

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值