[CF 526 F] Pudding Monsters(单调栈 + 线段树)

CF526F Pudding Monsters

problem

luogu翻译

solution

observation每行每列恰好有一个棋子,所以如果一段区间 [ l , r ] [l,r] [l,r] 会被某个 k k k 统计,当且仅当这个区间内棋子纵坐标 y m a x − y m i n + 1 = r − l + 1 y_{max}-y_{min}+1=r-l+1 ymaxymin+1=rl+1

发现这是经典的连续段计数问题

标配:单调栈 + + + 线段树。

将所有棋子按照横坐标升序排序。

枚举右端点 r r r,线段树上每个叶子节点 l l l 维护的是 [ l , r ] [l,r] [l,r] 的信息。

转换一下条件判定: y m a x − y m i n + l − r = 0 y_{max}-y_{min}+l-r=0 ymaxymin+lr=0

因为每行每列恰好一个棋子,所以所有情况都有 y m a x − y m i n ≥ r − l y_{max}-y_{min}\ge r-l ymaxyminrl

而最后要计入答案的是取等的情况,即最小值。

所以线段树就维护 y m a x − y m i n + l − r y_{max}-y_{min}+l-r ymaxymin+lr 的最小值,以及最小值个数。

最后统计的时候,就统计最小值为 0 0 0 的节点信息。

  • r r r 每次都是 + 1 +1 +1,对应的是线段树整体 − 1 -1 1

  • l l l r r r 无关,在最开始建树是加上即可。

  • y m a x y_{max} ymax,维护一个单增栈,栈中第 t o p top top 个元素维护的是区间 [ s M a x [ t o p − 1 ] + 1 , s M a x [ t o p ] ] \Big[sMax[top-1]+1,sMax[top]\Big] [sMax[top1]+1,sMax[top]] 的最大值信息,即这一段的纵坐标最大值都是 s M a x [ t o p ] sMax[top] sMax[top]

    每次用右端点 r r r 与栈比较纵坐标大小,如果 r r r 大则弹栈,将 t o p top top 维护的区间加上 y r − y s M a x [ t o p ] y_r-y_{sMax[top]} yrysMax[top]

  • y m i n y_{min} ymin,维护一个单减栈,栈中第 t o p top top 个元素维护的是区间 [ s M i n [ t o p − 1 ] + 1 , s M i n [ t o p ] ] \Big[sMin[top-1]+1,sMin[top]\Big] [sMin[top1]+1,sMin[top]] 的最大值信息,即这一段的纵坐标最小值都是 s M i n [ t o p ] sMin[top] sMin[top]

    每次用右端点 r r r 与栈比较纵坐标大小,如果 r r r 小则弹栈,将 t o p top top 维护的区间加上 y s M i n [ t o p ] − y i y_{sMin[top]-y_i} ysMin[top]yi

事件复杂度为 O ( n log ⁡ n ) O(n\log n) O(nlogn)

code

#include <bits/stdc++.h>
using namespace std;
#define maxn 300005
int n, tMax, tMin;
int x[maxn], y[maxn], id[maxn], sMax[maxn], sMin[maxn];
struct node { int tag, Min, cnt; }t[maxn << 2];

#define lson now << 1
#define rson now << 1 | 1
#define mid ( ( l + r ) >> 1 )

void pushup( int now ) {
    if( t[lson].Min < t[rson].Min ) t[now].Min = t[lson].Min, t[now].cnt = t[lson].cnt;
    else if( t[lson].Min > t[rson].Min ) t[now].Min = t[rson].Min, t[now].cnt = t[rson].cnt;
    else t[now].Min = t[lson].Min, t[now].cnt = t[lson].cnt + t[rson].cnt;
}

void pushdown( int now ) {
    if( t[now].tag ) {
        t[lson].tag += t[now].tag;
        t[rson].tag += t[now].tag;
        t[lson].Min += t[now].tag;
        t[rson].Min += t[now].tag;
        t[now].tag = 0;
        return;
    }
}

void build( int now, int l, int r ) {
    if( l == r ) { t[now].Min = l, t[now].cnt = 1; return; }
    build( lson, l, mid );
    build( rson, mid + 1, r );
    pushup( now );
}

void modify( int now, int l, int r, int L, int R, int k ) {
    if( R < l or r < L ) return;
    if( L <= l and r <= R ) { t[now].Min += k, t[now].tag += k; return; }
    pushdown( now );
    modify( lson, l, mid, L, R, k );
    modify( rson, mid + 1, r, L, R, k );
    pushup( now );
}

int query( int now, int l, int r, int L, int R ) { 
    if( R < l or r < L ) return 0;
    if( L <= l and r <= R ) return t[now].Min ? 0 : t[now].cnt;
    return query( lson, l, mid, L, R ) + query( rson, mid + 1, r, L, R );
}

int main() {
    scanf( "%d", &n );
    for( int i = 1;i <= n;i ++ ) scanf( "%d %d", &x[i], &y[i] ), id[i] = i;
    sort( id + 1, id + n + 1, []( int u, int v ) { return x[u] < x[v]; } );
    build( 1, 1, n );
    long long ans = 0;
    for( int i = 1;i <= n;i ++ ) {
        modify( 1, 1, n, 1, n, -1 );
        modify( 1, 1, n, x[sMax[tMax]] + 1, i, y[id[i]] );
        modify( 1, 1, n, x[sMin[tMin]] + 1, i, -y[id[i]] );
        while( tMax and y[sMax[tMax]] < y[id[i]] ) {
            modify( 1, 1, n, x[sMax[tMax - 1]] + 1, x[sMax[tMax]], y[id[i]] - y[sMax[tMax]] );
            tMax --;
        }
        while( tMin and y[sMin[tMin]] > y[id[i]] ) {
            modify( 1, 1, n, x[sMin[tMin - 1]] + 1, x[sMin[tMin]], y[sMin[tMin]] - y[id[i]] );
            tMin --;
        }
        sMax[++ tMax] = sMin[++ tMin] = id[i];
        ans += query( 1, 1, n, 1, i );
    }
    printf( "%lld\n", ans );
    return 0;
}
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值