题意
小红有n个朋友。她准备邀请一些朋友请他们吃饭。已知第ii个朋友的财富值为ai,小红邀请他带来的愉悦值为bi。如果小红邀请的两个朋友的财富值为x和y,那么他们之间就会产生∣x−y∣的隔阂。小红这桌饭局的隔阂值为隔阂最大的那一对朋友的隔阂,这桌饭局的愉悦值为所有受邀朋友的愉悦值总和。小红希望这桌饭局的愉悦值至少达到kk。她想知道最终饭局隔阂的最小值是多少?
思路
隔阂值的定义是隔阂最大的那一对朋友的隔阂,求最后满足xx条件的隔阂的最小值是多少?
这其实可以理解为一个最大值最小化问题,一般推一下单调性的话可以用二分来求。
单调性说明:先按财富值从小到大排序,权值都是正整数,假设现在的饭局是朋友i~j参加,那么多一个朋友的话,愉悦值肯定会增大,而隔阂值也一定会增大;反之同理。
问题转化为check函数:即判断隔阂值最大为mid的时候,愉悦值能否到达k
这里也是比较经典的一个问题,相当于选一段隔阂值<=mid的区间,看愉悦值的最大值。
最朴素的思路就是n^2的暴力,枚举区间长度(通过隔阂值)和区间起点,维护最大值。
但是题意里其实也提到了愉悦值都是正数,贪心地去维护最大值的话,肯定是选择隔阂值尽可能地接近mid的区间,多一个元素对答案的贡献是正数。所以就可以枚举起点,找隔阂值<=mid的最大点,并且O(1)的求这段区间的愉悦值之和。
假设当前枚举的起点是下标i,隔阂值<=mid的最大点实际上就是找小于等于a[i]+mid的最后一个元素,这里又延伸出2种方法:
- 二分找>a[i]+mid的第一个元素,如果这个元素不是最后一个元素的话,让下标减一,就是小于等于a[i]+mid的最后一个元素。
- 直接二分找<=a[i]+mid的最后一个元素,这里最好要判断下如果最后一个元素<=a[i]+mid,直接返回n-1
分别对应下面的代码1和代码2,细节比较多,每个人习惯不一样,可以自行把握。注意点就是如果找不到的话返回最后一个元素的下标
O(1)的求这段区间的愉悦值之和 可以用前缀和维护
这样整体的时间复杂度是O(logn*n*logn)
当枚举区间时,可以通过双指针/滑动窗口减少不必要的状态计算从而降低时间复杂度。比如:如果[1,5]不满足隔阂值的条件,但是[2,5]满足隔阂值的条件的话,那么[1,6]肯定不满足隔阂值的条件,[2,6]可能满足隔阂值的条件,其实[1,6]就不用枚举了。所以对于每一个右端点来说,维护最远的左端点,每次枚举到新的右端点的时候,先将右端点的贡献加入,然后移动左端点使得其称为满足条件的最远的左端点,再维护最大值即可。这样的时间复杂度降到了O(logn*n)
二分套二分代码1
package main import ( "fmt" "sort" ) type Friend struct { money int happy int } func main() { /* 相当于一个最大值最小化问题 二分 隔阂的最小值 check的时候 判断隔阂值为mid的时候 愉悦值能否到达k 二分或是滑动窗口做 */ var n, k int fmt.Scan(&n, &k) friends := make([]Friend, n) for i := 0; i < n; i++ { fmt.Scan(&friends[i].money) } for i := 0; i < n; i++ { fmt.Scan(&friends[i].happy) } //按财富值排序 sort.Slice(friends, func(i, j int) bool { return friends[i].money < friends[j].money }) //fmt.Println(friends) l, r, ans := 0, 1_000_000_010, -1 sum := make([]int, n+2) //前缀和 for i := 0; i < n; i++ { if i == 0 { sum[i] = friends[i].happy continue } sum[i] = sum[i-1] + friends[i].happy } find := func(x int) int { //找到第一个>x 的元素 ll, rr, res := 0, n-1, n-1 for ll <= rr { mid := (ll + rr + 1) / 2 if friends[mid].money > x { res = mid rr = mid - 1 } else { ll = mid + 1 } } return res } check1 := func(x int) bool { //判断隔阂值最大为x的时候 愉悦值能否到达k for i := 0; i < n; i++ { //找到第一个>a[i].money + x 的元素 pos := find(friends[i].money + x) if pos != n-1 { pos-- } //fmt.Println(x, i, friends[i].money+x, pos) now := 0 if i >= 1 { now = sum[i-1] } if pos >= i && sum[pos]-now >= k { return true } } return false } //二分隔阂值 for l <= r { mid := (l + r + 1) / 2 // fmt.Println(mid, check1(mid)) if check1(mid) { ans = mid r = mid - 1 } else { l = mid + 1 } } fmt.Println(ans) }
二分套二分代码2
package main import ( "fmt" "sort" ) type Friend struct { money int happy int } func main() { /* 相当于一个最大值最小化问题 二分 隔阂的最小值 check的时候 判断隔阂值为mid的时候 愉悦值能否到达k 二分或是滑动窗口做 */ var n, k int fmt.Scan(&n, &k) friends := make([]Friend, n) for i := 0; i < n; i++ { fmt.Scan(&friends[i].money) } for i := 0; i < n; i++ { fmt.Scan(&friends[i].happy) } //按财富值排序 sort.Slice(friends, func(i, j int) bool { return friends[i].money < friends[j].money }) //fmt.Println(friends) l, r, ans := 0, 1_000_000_010, -1 sum := make([]int, n+2) //前缀和 for i := 0; i < n; i++ { if i == 0 { sum[i] = friends[i].happy continue } sum[i] = sum[i-1] + friends[i].happy } find := func(x int) int { if friends[n-1].money <= x { return n-1 } //找到<=x的最后一个元素 ll, rr, res := 0, n-1, n-1 for ll <= rr { mid := (ll + rr + 1) / 2 if friends[mid].money <= x { res = mid ll = mid + 1 } else { rr = mid - 1 } } return res } check1 := func(x int) bool { //判断隔阂值最大为x的时候 愉悦值能否到达k for i := 0; i < n; i++ { //找到第一个>a[i].money + x 的元素 再减去1 //最后一个<=a[i].money+x的最后一个元素 pos := find(friends[i].money + x) //fmt.Println(x, i, friends[i].money+x, pos) now := 0 if i >= 1 { now = sum[i-1] } if pos >= i && sum[pos]-now >= k { return true } } return false } //二分隔阂值 for l <= r { mid := (l + r + 1) / 2 // fmt.Println(mid, check1(mid)) if check1(mid) { ans = mid r = mid - 1 } else { l = mid + 1 } } fmt.Println(ans) }
滑动窗口代码
package main import ( "fmt" "sort" ) type Friend struct { money int happy int } func main() { /* 相当于一个最大值最小化问题 二分 隔阂的最小值 check的时候 判断隔阂值为mid的时候 愉悦值能否到达k 二分或是滑动窗口做 */ var n, k int fmt.Scan(&n, &k) friends := make([]Friend, n) for i := 0; i < n; i++ { fmt.Scan(&friends[i].money) } for i := 0; i < n; i++ { fmt.Scan(&friends[i].happy) } //按财富值排序 sort.Slice(friends, func(i, j int) bool { return friends[i].money < friends[j].money }) //fmt.Println(friends) l, r, ans := 0, 1_000_000_010, -1 check2 := func(x int) bool { //判断隔阂值最大为x的时候 愉悦值能否到达k //双指针 ll, rr, now := 0, 0, 0 for ; rr < n; rr++ { //入 now += friends[rr].happy for ll <= rr && friends[rr].money-friends[ll].money > x { now -= friends[ll].happy ll++ } //判断 if now >= k { return true } } return false } //二分隔阂值 for l <= r { mid := (l + r + 1) / 2 //fmt.Println(mid, check2(mid)) if check2(mid) { ans = mid r = mid - 1 } else { l = mid + 1 } } fmt.Println(ans) }