ABC203F - Weed

F - Weed

题目描述

n n n 株艹,第 i i i 株的高度为 a i a_i ai ,你可以预先拔掉不超过 k k k 株艹,然后按如下方式操作:

  • 选取没拔掉的艹中最高的艹(高度 h h h ),一次拔掉所有高度 > ⌊ h 2 ⌋ >\lfloor \frac{h}{2}\rfloor >2h 的艹。

你需要在操作次数最少的情况下,最小化预先拔掉的艹的数量。

数据范围与提示

1 ≤ n ≤ 2 × 1 0 5 , 0 ≤ k ≤ n , 1 ≤ a i ≤ 1 0 9 1\le n\le 2\times 10^5,0\le k\le n,1\le a_i\le 10^9 1n2×105,0kn,1ai109

前言

比赛刚刚结束,我就想到怎么做了。

回想我E题要是能一遍过,而不去调那20分钟,我肯定可以把F题打出来然后闲坐10分钟。

都是自己代码能力不好,写代码之前不多思考一点细节。

教练说我不要一想到做法就急着开始打,要多揣摩一下。我何时才能改掉?

思路

由于每次选取最大的草后把所有大于它高度一半的都拔除,相当于最高的草的高度至少减半,所以最多操作 log ⁡ a i \log a_i logai 次。

所以最小操作数完全可以枚举,我们只需要考虑在每种操作数的情况下最小化预先拔掉的草的数量。

把草按 a a a 从小到大排序后,我们发现一次操作必定为一个区间,且区间右端点固定则左端点固定,预先拔掉的草就是除了区间覆盖的草以外的其它草,前后显然可转移。

所以设 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示操作 j j j 次拔完前 i i i 株草(当然是排序后)的情况下需要预先拔掉的草的数量的最小值,显然有
d p [ i ] [ j ] = min ⁡ ( d p [ i − 1 ] [ j ] + 1 , d p [ p i − 1 ] [ j − 1 ] ) dp[i][j]=\min(dp[i-1][j]+1,dp[p_i-1][j-1]) dp[i][j]=min(dp[i1][j]+1,dp[pi1][j1])
其中 p i p_i pi 表示 i i i 左边的最靠左的高度大于 ⌊ a i 2 ⌋ \lfloor \frac{a_i}{2}\rfloor 2ai 的草的下标,这个可以预先用二分或倍增处理出来。总复杂度 O ( n log ⁡ a ) O(n\log a) O(nloga)

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#define ll long long
#define MAXN 200105
#define uns unsigned
#define MOD 998244353ll
#define INF 0x7f7f7f7f
using namespace std;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+s-'0',s=getchar();
	return f?x:-x;
}
int n,k,a[MAXN],dp[MAXN][35],mn,mk;
signed main()
{
	n=read(),k=read();
	for(int i=1;i<=n;i++)a[i]=read();
	sort(a+1,a+1+n);
	memset(dp,0x3f,sizeof(dp));
	dp[0][0]=0;
	mn=INF,mk=INF;
	for(int i=1;i<=n;i++){
		dp[i][0]=i;
		int p=i;
		for(int j=20;j>=0;j--)
			if(p-(1<<j)>0&&a[p-(1<<j)]>(a[i]>>1))p-=(1<<j);
		for(int j=0;j<=31;j++){
			dp[i][j]=min(dp[i][j],dp[i-1][j]+1);
			if(j>0)dp[i][j]=min(dp[i][j],dp[p-1][j-1]);
		}
	}
	for(int j=0;j<=31;j++){
		if(dp[n][j]<=k&&j<mn)mn=j,mk=dp[n][j];
		else if(dp[n][j]<=k&&j==mn)mk=min(mk,dp[n][j]);
	}
	printf("%d %d\n",mn,mk);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值