0
点赞
收藏
分享

微信扫一扫

2021牛客暑期多校训练营5 - K(King of Range)

​​https://ac.nowcoder.com/acm/contest/11256/K​​

​解法1: ST表 + 双指针​

#include <iostream>

using namespace std;

int mx[100010][25], mn[100010][25], n, m, a[100010], cc[100010], k;

void rmq_init(){

for (int j = 1; (1 << j) <= n; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
mn[i][j] = min(mn[i][j-1], mn[i + (1 << (j-1))][j-1]);
mx[i][j] = max(mx[i][j-1], mx[i + (1 << (j-1))][j-1]);
}
}
// 小于等于len的最大的2的k次幂
for (int len = 1; len <= n; ++len) {
int k = 0;
while ((1 << (k + 1)) <= len){
k++;
}
cc[len] = k;
}
}

int rmq_min(int L, int R){

int kk = cc[R-L+1];
return min(mn[L][kk], mn[R-(1<<kk)+1][kk]);
}

int rmq_max(int L, int R){

int kk = cc[R-L+1];
return max(mx[L][kk], mx[R-(1<<kk)+1][kk]);
}


int main(){

scanf("%d%d", &n, &m);

for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
mn[i][0] = a[i];
mx[i][0] = a[i];
}

rmq_init();
long long res;
while (m--){
scanf("%d", &k);

int l, r = 1;
res = 0;

for (l = 1; l <= n; ++l) {

while(r <= n && rmq_max(l, r) - rmq_min(l, r) <= k){
r++;
}

if(rmq_max(l, r) - rmq_min(l, r) > k){
res += (n - r + 1);
}else{
break;
}
}
printf("%lld\n", res);
}

return 0;
}

​解法二: 单调队列​

可以发现,实际要找的区间(​​l​​​, ​​r​​​)的​​l​​​是从​​1, 2....x​​​,逐渐上升的,​​x​​​ 代表:以(​​x + 1​​​)区间为起点时,​​r​​​向右扩展(直到​​r​​​⇒ ​​n​​​),在这个区间也找不出满足条件的两个数,并且我们可以发现,当找到一个离​​l​​​最近的两个满足条件的数时,这两个满足条件的数的较右边的数的位置为​​r​​​,对于(​​l​​​, ​​r​​​)区间,当​​l​​​不变拓展r向右时(直到​​r == n​​​),这些区间一定也满足条件,所以,以​​l​​​为起点的区间,满足条件的有​​n - r + 1​​​个,所以现在要求的是,离​​l​​​最近的满足条件的两个数。利用单调队列的性质,固定r,每次找到两个数(​​ai​​​, ​​aj​​​)(​​i < j​​​,并且​​ai​​​是离​​ar​​​最近的),此时(​​l​​​, ​​j​​​)满足上面说的区间特性,所以​​res += (n - j + 1)​​​ ​​l++​​​,如果此时​​l <= i​​​,说明(​​l​​​, ​​r​​​)任然包含(​​ai​​​, ​​ar​​​),所以继续有​​res += (n - j + 1)​​​,直到​​l > i​​​,并在单调队列弹出 ​​i​​。

而对于利用单调队列找满足条件的(​​ai​​​,​​aj​​​),可以看成是固定​​j​​​的位置,找到离​​j​​​最近的满足​​i​​​的位置 ! ! ! ! ! !
​​​---------------------------------------------------------------​​​ 而对于​​mx​​数组,维护一个单调递减队列,当当前值比​​mx[t]​​(队尾元素)大于等于时,明显:当前元素对于等会的离 ​​j​​ 最近的元素的贡献更好(会更靠近​​j​​,并且对差值的贡献更大),所以弹出队尾元素。
​---------------------------------------------------------------​​ 对于​​mi​​数组,维护一个单调递增队列,当当前值比​​mi[t]​​(队尾元素)小于等于时,明显:当前元素对于等会的离 j 最近的元素的贡献更好(会更靠近​​j​​,并且对差值的贡献更大),所以弹出队尾元素。

​​另一种思路可以参考此佬写的题解​​

#include<iostream>

using namespace std;

const int N = 1e5 + 5;

int n, m, k, a[N], mi[N], mx[N];

int main(){

scanf("%d%d", &n, &m);

for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}

while (m--){

scanf("%d", &k);

int h1 = 1, t1 = 0, h2 = 1, t2 = 0;
long long ans = 0;
int ll = 1;

for (int i = 1; i <= n; ++i) {

while (h1 <= t1 && a[i] >= a[mx[t1]]){
t1--;
}
mx[++t1] = i;

while (h2 <= t2 && a[i] <= a[mi[t2]]){
t2--;
}
mi[++t2] = i;

while (a[mx[h1]] - a[mi[h2]] > k){

ans += (n - i + 1);
ll++;

if(mx[h1] < ll){
h1++;
}

if(mi[h2] < ll){
h2++;
}
}
}

printf("%lld\n", ans);
}


return 0;
}


举报

相关推荐

0 条评论