题目传送门
题意:
n个球,每个球上有一个数是ai,两个整数k和d。现在想让你把n个球分成若干堆,对于堆数没有限制,每堆包括至少k个球,每堆数中最大值减最小值不超过d。问你是否可以进行这样的分堆。
数据范围:1 <= k <= n <= 5e5 ,0 <= d <= 1e9 ,1 <= ai <= 1e9。
题解:
在想到当前堆分更多数可能使结果更优时,就应该意识到是dp。
dp[i] = 1表示前i个数是可以进行满足要求的分堆的,dp[i] = 0表示前i个数是不可以进行满足要求的分堆的。
0个数是合法的,这是起始状态,即dp[0] = 1。
把这n个数排序,从小到大遍历。
两个限制可以得出合法区间[L , R],L是满足aL >= ai - d最小的下标,R <= i - k。
如果dp[j] = 1 且 j∈[L , R],则dp[i] = 1。
找dp[j]的过程就是求dp数组在[L . R]区间最大值的过程。
我没有开dp数组,因为我把状态存线段树里面了。
感受:
初始状态设置时候忘记更改一个参数了,导致2A。
还是不够稳。
这种题当你意识到是dp时已经会做了,水平不同的选手意识到的时间不同,我还是一眼不了,得想一会。
确实水平提高了一些,以前碰到这种题,想都不想就去看题解了,现在能独立做出来了(虽说看了错误测例)。
代码:
#include<bits/stdc++.h>
using namespace std ;
typedef long long ll ;
const int maxn = 5e5 + 5 ;
int n , k , d ;
int a[maxn] ;
bool max1[maxn << 2] ;
int ls(int x)
{
return x << 1 ;
}
int rs(int x)
{
return x << 1 | 1 ;
}
void update(int id , int l , int r , int x , bool y)
{
int mid = (l + r) / 2 ;
if(l == r && l == x)
{
max1[id] = y ;
return ;
}
if(x <= mid)
update(ls(id) , l , mid , x , y) ;
else
update(rs(id) , mid + 1 , r , x , y) ;
max1[id] = max(max1[ls(id)] , max1[rs(id)]) ;
}
bool query(int id , int l , int r , int x , int y)
{
if(x > y) return 0 ;
bool ans = 0 ;
int mid = (l + r) / 2 ;
if(x <= l && r <= y)
return max1[id] ;
if(x <= mid) ans = max(ans , query(ls(id) , l , mid , x , y)) ;
if(y > mid) ans = max(ans , query(rs(id) , mid + 1 , r , x , y)) ;
return ans ;
}
void solve(int i)
{
int l = lower_bound(a + 1 , a + i + 1 , a[i] - d) - a ;
int r = i - k ;
l -- ;
l = max(l , 0) ;
bool x = query(1 , 0 , n , l , r) ;
if(x) update(1 , 0 , n , i , 1) ;
}
int main()
{
scanf("%d%d%d" , &n , &k , &d) ;
for(int i = 1 ; i <= n ; i ++) scanf("%d" , &a[i]) ;
sort(a + 1 , a + n + 1) ;
update(1 , 0 , n , 0 , 1) ;
for(int i = 1 ; i <= n ; i ++)
solve(i) ;
if(query(1 , 0 , n , n , n)) printf("YES\n") ;
else printf("NO\n") ;
return 0 ;
}