题目描述
有 n n n 株艹,第 i i i 株的高度为 a i a_i ai ,你可以预先拔掉不超过 k k k 株艹,然后按如下方式操作:
你需要在操作次数最少的情况下,最小化预先拔掉的艹的数量。
数据范围与提示
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 1≤n≤2×105,0≤k≤n,1≤ai≤109 。
比赛刚刚结束,我就想到怎么做了。
回想我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[i−1][j]+1,dp[pi−1][j−1])
其中
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;
}