其他分享
首页 > 其他分享> > 所有区间问题的套路

所有区间问题的套路

作者:互联网

改题改题改题!!!做完题一定要好好改真的会继续两次NoNoNo继续n次遇见它!!!

C. 完美子图

线段树做法:考虑把一个区间向右拓展,新增加的一个位置对以前所有处理过的区间的影响,当然对自己这一个长度的影响是1,第一层循环枚举R,新加入的数能对过去的区间会造成影响只有两种情况:加入的新数成为了某段以i为右边界的区间的最大值或最小值,这个可以用单调栈来维护。

把图转成二维的之后问题就变成了max-min=r-l,但因为这是一个排列(代表没有重复),对于所有区间来说max-min>=r-l一定成立,所以合法的区间就是l+max-min的最小值=r,线段树上维护了l+max-min的最小值以及满足这个最小值的区间个数,建树的时候把 l 先放进去,长度只有1的时候max-min=0,正好是初始状态,更新时,(以最大值为例)新的最大值是a[i],被他影响到的上一个最大值是a[mx[t_mx]],虽然还可能有更多的被影响,但是要在每次出栈是更新因为“过去的最大值”变化了,而最大值的增量就是a[i]-a[mx[t_mx]],因为lazy放的是对最小值的更新所以当然不用*区间长度。

因为对于一个区间来说,max-min=r-l是它的最小情况,所以左右子树向上合并时,min相同才可能同时合法,min大的那个绝对不合法(已有比它小的它就不可能最小了),同样query的时候也不能把左右直接相加,分类讨论一下就好了。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 5e4 + 2;
const ll mod = 998244353;
const int INF = 2147483647;

int n, a[maxn], mx[maxn], mi[maxn], t_mx, t_mi;
ll ans;

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

struct node 
{
    int ls, rs, cnt, min, lazy;
}tree[maxn<<2];
int tot;

#define lson tree[rt].ls
#define rson tree[rt].rs 

void pushup(int rt)
{
    if(tree[lson].min == tree[rson].min)
    {
        tree[rt].min = tree[lson].min; tree[rt].cnt = tree[lson].cnt+tree[rson].cnt;
        return;
    }
    if(tree[lson].min > tree[rson].min)
    {
        tree[rt].min = tree[rson].min; tree[rt].cnt = tree[rson].cnt;
        return;
    }
    tree[rt].min = tree[lson].min; tree[rt].cnt = tree[lson].cnt;
}

void pushdown(int rt)
{
    if(tree[rt].lazy)
    {
        int lz = tree[rt].lazy;
        tree[rt].lazy = 0;
        tree[lson].lazy += lz;
        tree[rson].lazy += lz;
        tree[lson].min += lz;
        tree[rson].min += lz;
    }
}

int build(int rt, int l, int r)
{
    if(!rt) rt = ++tot;
    if(l == r)
    {
        tree[rt].cnt = 1; tree[rt].min = l;
        return rt;
    }
    int mid = (l + r) >> 1;
    tree[rt].ls = build(lson, l, mid);
    tree[rt].rs = build(rson, mid+1, r);
    pushup(rt);
    return rt;
}

void update(int rt, int l, int r, int L, int R, int val)
{
    if(L <= l && r <= R)
    {
        tree[rt].lazy += val; tree[rt].min += val;
        return;
    }
    pushdown(rt);
    int mid = (l + r) >> 1;
    if(L <= mid) update(lson, l, mid, L, R, val);
    if(R > mid) update(rson, mid+1, r, L, R, val);
    pushup(rt);
}

struct node2 
{
    int id, v;
    node2(){}
    node2(int x, int y) {id = x, v = y;}
};

node2 query(int rt, int l, int r, int L, int R)
{
    if(L <= l && r <= R)
    {
        return node2(tree[rt].cnt, tree[rt].min);
    }
    pushdown(rt);
    int mid = (l + r) >> 1;
    node2 left, right;
    left.v = right.v = 1e9;
    if(L <= mid) left = query(lson, l, mid, L, R);
    if(R > mid) right = query(rson, mid+1, r, L, R);
    if(left.v < right.v) return left;
    if(right.v < left.v) return right;
    left.id += right.id;
    return left;
}

int main()
{
    n = read();
    for(int i=1; i<=n; i++)
    {
        int x = read(), y = read();
        a[x] = y;
    }
    tot = 1;
    int root = build(1, 1, n);
    for(int i=1; i<=n; i++)
    {
        while(t_mx > 0 && a[mx[t_mx]] < a[i])
        {
            update(root, 1, n, mx[t_mx-1]+1, mx[t_mx], a[i]-a[mx[t_mx]]);
            t_mx--;
        }
        mx[++t_mx] = i;
        while(t_mi > 0 && a[mi[t_mi]] > a[i])
        {
            update(root, 1, n, mi[t_mi-1]+1, mi[t_mi], a[mi[t_mi]]-a[i]);
            t_mi--;
        }
        mi[++t_mi] = i;
        node2 cat = query(1, 1, n, 1, i);
        if(cat.v == i) ans += (ll)cat.id;
    }

    printf("%lld\n", ans);
    
    return 0;
}
View Code

——感谢caorong

还可以用cdq分治:在合并的时候还是把区间想像成左右两部分,分类讨论一下:两个最值都在左; 都在右; min在左,max在右;min在右,max在左4种情况,也就是每次考虑跨过中点的区间答案,细节注释在了代码里。

#include <bits/stdc++.h>

using namespace std;

typedef unsigned long long ll;
const int maxn = 50003;
const ll mod = 998244353;
const int INF = 0x7ffffff;

int a[maxn], Min[maxn], Max[maxn], cnt[maxn<<1];
int ans, n;

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

void solve(int l, int r)
{
    if(l == r) 
    {
        ans++; return;
    }
    int mid = (l + r) >> 1;
    solve(l, mid); solve(mid+1, r);
    Min[mid] = Max[mid] = a[mid];
    Min[mid+1] = Max[mid+1] = a[mid+1];

    for(int i=mid-1; i>=l; i--)
    {
        Min[i] = min(Min[i+1], a[i]);
        Max[i] = max(Max[i+1], a[i]);
    }
    for(int i=mid+2; i<=r; i++)
    {
        Min[i] = min(Min[i-1], a[i]);
        Max[i] = max(Max[i-1], a[i]);
    }
    //两个最值都在左区间,
    for(int i=mid; i>=l; i--)
    {
        int j=i+Max[i]-Min[i];//如果通过最值找到的右端点合法
        if(j<=r && j>mid && Max[i]>Max[j] && Min[i]<Min[j]) ans++;
    }
    for(int j=mid+1; j<=r; j++)
    {
        int i=j-Max[j]+Min[j];
        if(i>=l && i<=mid && Max[i]<Max[j] && Min[i]>Min[j]) ans++;
    }
    int j = mid+1, k = mid+1;
    for(int i=mid; i>=l; i--)//左小右大
    {
        while(j<=r && Min[j]>Min[i])//j满足左小,j是mid+1到右边界的区间,而不是右端点
        {
            cnt[Max[j]-j+n]++;//在干嘛?所以j是一个符合条件的区间,Max[j]-j==Min[i]-i
            j++;
        }
        //Max是单调的,越接近越可能会合法,和上面的Min不同,它反向了
        //j不变,因为Min[i]是区间最小值,只有可能变得更小或不变,而变得更小就使得j的范围更大
        while(k<j && Max[i]>Max[k])//为了避免重复,一定要满足右大,可是为什么要比j小?而且,k是右区间,
        //这是在清理重复的情况,把左大的删掉
        {
            cnt[Max[k]-k+n]--;
            k++;
        }
        //同上,k不需要清空
        ans += cnt[Min[i]-i+n];//为了满足区间长度的条件,上面存的只是可能合法的(左小右大的)
    }
    while(k<j)
    {
        cnt[Max[k]-k+n]--;//其实就是清空数组的意思,memset会TLE
        k++;
    }
    j = mid, k = mid;
    for(int i=mid+1; i<=r; i++)
    {
        while(j>=l && Min[i]<Min[j])
        {
            cnt[Max[j]+j]++;
            j--;
        }
        while(k>j && Max[k]<Max[i])
        {
            cnt[Max[k]+k]--;
            k--;
        }
        ans += cnt[Min[i]+i];//Max[i]-Min[j]==j-i,移项
    }
    while(k>j)
    {
        cnt[Max[k]+k]--;
        k--;
    }
}

int main()
{
    n = read();
    for(int i=1; i<=n; i++)
    {
        int x = read(), y = read();
        a[x] = y;
    }
    solve(1, n);
    printf("%d", ans);
    
    return 0;
}
View Code

B. 任意模数快速插值

未完待续……

标签:rt,min,套路,所有,tree,mid,int,区间,mx
来源: https://www.cnblogs.com/Catherine2006/p/16536376.html