题目地址:https://nanti.jisuanke.com/t/41296

题目:


给出一个序列,问有多少个区间使得这个区间内所有的数sort之后相邻两数的差值不超过1

 

解题思路:


max/min:区间最大/小值,cnt:区间不同数的个数,满足条件的Continuous Intervals的一个性质:

max-min+1=cnt,即max-min-cnt=-1,且最小值是-1

枚举区间右边界R,那么区间左边界L的取值范围是一个区间:[1,R]

边输入边处理,若处理到第i个数,[i,i]定是一个满足条件的区间,其他需要判断的区间是[j,i](j<i),而[j,i]只是在[j,i-1]后面加了个a[i],[j,i-1]在前面已经处理过了。

对于a[i]:

1⃣️如果它能取代[j,i-1]中的最小值,那么[j,i]这个区间的min就要被更换,对应的式子max-min-cnt就要改变相应的值;

2⃣️同理,如果它能取代[j,i-1]中的最大值,那么[j,i]对应的式子也要改变相应的值,

3⃣️同时也要更新区间的cnt值。

 

1⃣️2⃣️可以用单调栈和线段树的区间更新解决:

考虑如果要找一个数x左边第一个小于它的数,记为L[x],那么在使用单调栈的过程中,每次处理完,单调栈中存的数(栈底称为最下面,栈顶称为最上面)下面的数一定是L[紧邻的上面的数]。若此时单调栈中下面的数是a,上面的数是b,那么L[b]=a,原序列中[pos[a]+1,pos[b]-1]之间的数都因为大于b而被移除了栈,所以对于pos[a]+1 ≤ j ≤pos[b](j的范围下同),[j,pos[b]]的区间最小值是b,若待插入的是第i个数k,且k<=b,即k可以取代b成为[j,pos[b]]的区间最小值,那么[j,i]的区间最小值就要更新成k了,对应式子变成max-(min-(b-k))-cnt=max-min-cnt+(b-k),线段树区间更新即可,但是k<=b ,a<b,k和a的关系未知,仍要继续判断处理

考虑找一个数x左边第一个大于它的数,也是同样的处理思路,某些区间的式子变成max+-min-cnt

3⃣️用线段树区间更新解决:

我们没必要求出每个区间cnt的值具体是多少,只要能确定a[i]=k的出现对以a[i]为最后一个元素的区间的cnt是否有改变,若k上一次出现的位置是pos[k], 那么pos[k]+1 ≤ j ≤ i, [j,i]区间内都增加了一个新的元素,式子变成max-min-(cnt+1)=max-min-cnt-1

最后就是如何利用线段树快速统计区间max-min-cnt值=-1的所有区间数,每次添加一个元素[i,i]这个区间的式子值是-1,且区间式子值不可能比-1小,所以如果两边都是-1的话,统计和,否则统计式子值小的那一侧,总之方法很巧妙(我想不到的那种)

 

ac代码:


#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5+10;
typedef long long ll;
int t, n, k;
struct node{
    int pos, v;
}mx[maxn], mn[maxn];
int nx, nn;
int sum[maxn*4], lazy[maxn*4], val[maxn*4];//val=max-min-cnt
unordered_map<int, int> m;//记录上一次出现的位置
void push_up(int id)
{
    val[id] = min(val[id<<1], val[id<<1|1]);
    if(val[id<<1] == val[id<<1|1]) sum[id] = sum[id<<1] + sum[id<<1|1];
    else if(val[id<<1] < val[id<<1|1]) sum[id] = sum[id<<1];
    else sum[id] = sum[id<<1|1];
}
void push_down(int id)
{
    lazy[id<<1] += lazy[id];
    lazy[id<<1|1] += lazy[id];
    val[id<<1] += lazy[id];//只更新到了val[id],下面的也要更新
    val[id<<1|1] += lazy[id];
    lazy[id] = 0;
}
void build(int id, int l, int r, int p)
{
    if(l == r)
    {
        val[id] = -1;//【p,p】满足条件
        sum[id] = 1;
        return ;
    }
    int mid = (l + r) >> 1;
    if(p <= mid) build(id<<1, l, mid, p);
    else build(id<<1|1, mid+1, r, p);
    push_up(id);
}
void update(int id, int l, int r, int ql, int qr, int v)//区间更新
{
    if(ql <= l && r <= qr)
    {
        val[id] += v;
        lazy[id] += v;
        return ;
    }
    if(lazy[id]) push_down(id);
    int mid = (l + r) >> 1;
    if(ql <= mid) update(id<<1, l, mid, ql, qr, v);
    if(qr > mid) update(id<<1|1, mid+1, r, ql, qr, v);
    push_up(id);
}
int main()
{
    //freopen("/Users/zhangkanqi/Desktop/11.txt","r",stdin);
    int Case = 0;
    scanf("%d", &t);
    while(t--)
    {
        ll ans = 0;
        memset(sum, 0, sizeof sum);
        memset(lazy, 0, sizeof lazy);
        mx[0]={0,0}, mn[0]={0,0};
        nx = nn = 0;
        m.clear();
        scanf("%d", &n);
        for(int i = 1; i <= n; i++)
        {
            scanf("%d", &k);
            build(1, 1, n, i);
            while(nx > 0 && mx[nx].v <= k) // max
            {
                int pos = mx[nx].pos;
                int v = k-mx[nx].v;
                update(1, 1, n, mx[--nx].pos+1, pos, v);
            }
            mx[++nx] = {i, k};
            while(nn >0 && mn[nn].v >= k) // min
            {
                int pos = mn[nn].pos;
                int v = mn[nn].v-k;
                update(1, 1, n, mn[--nn].pos+1, pos, v);
            }
            mn[++nn] = {i, k};
            int L;
            if(m[k]) L = m[k] + 1;
            else L = 1;
            m[k] = i;
            update(1, 1, n, L, i, -1);
            ans += sum[1];
        }
        printf("Case #%d: %lld\n", ++Case, ans);
    }
}