JZOJ4848. 永恒的契约

题目大意

给定一个长度为 n 的环,每个位置有一个高度ai
当在环上的两条路径中有至少一条路径上除了两个端点,路径上的高度都不大于 min(ai,aj) 时, i,j 可以互相看到。
求有多少对可以互相看到的点对。
T组数据。

Data Constraint
T5,n1000000

题解

因为是环,所以可以先copy一遍,转成序列问题。
每个位置可以看到对方,当且仅当这两个位置之间没有比他们高的位置。
所以可以维护一个双向的单调递减队列,每次有元素进入时就计算它对前面的贡献。
至于相同的元素,我们可以把它们压在一起,再搞个链表维护一下。
注意要减去重复的方案,统计一下最大值和最小值的个数就好了。

时间复杂度: O(n)

SRC

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std ;

#define N 2000000 + 10
typedef long long ll ;
struct Note {
    int v , w , same , samA , Head ;
} S[N] ;

int a[N] , Next[N] ;
int T , n , tot1 , tot2 , FMax , SMax ;
ll ans ;

inline int Read() {
    int ret = 0 ;
    char ch = getchar() ;
    while ( ch < '0' || ch > '9' ) ch = getchar() ;
    while ( ch >= '0' && ch <= '9' ) ret = ret * 10 + ch - '0' , ch = getchar() ;
    return ret ;
}

int main() {
    freopen( "forever.in" , "r" , stdin ) ;
    freopen( "forever.out" , "w" , stdout ) ;
    T = Read() ;
    while ( T -- ) {
        memset( S , 0 , sizeof(S) ) ;
        memset( Next , 0 , sizeof(Next) ) ;
        FMax = SMax = 0 ;
        ans = tot1 = tot2 = 0 ;
        n = Read() ;
        for (int i = 1 ; i <= n ; i ++ ) {
            a[i] = Read() , a[n+i] = a[i] ;
            if ( a[i] > FMax ) {
                SMax = FMax ;
                FMax = a[i] ;
                tot2 = tot1 ;
                tot1 = 0 ;
            } else if ( a[i] > SMax ) SMax = a[i] , tot2 = 0 ;
            if ( a[i] == FMax ) tot1 ++ ;
            if ( a[i] == SMax ) tot2 ++ ;
        }
        if ( n < 2 ) { printf( "0\n" ) ; continue ; }
        if ( FMax == SMax ) {
            ans -= (ll)tot1 * (tot1 - 1) / 2 ;
        } else ans -= (ll)tot1 * tot2 ;
        int head = 1 , tail = 0 ;
        for (int i = 1 ; i <= 2 * n ; i ++ ) {
            if ( head <= tail && i - S[head].w >= n ) {
                S[head].samA -- ;
                if ( S[head].same ) S[head].same -- ;
                if ( !S[head].samA ) {
                    S[head].v = S[head].w = S[head].same = S[head].samA = 0 ;
                    head ++ ;
                } else S[head].w = Next[S[head].w] ;
            }
            while ( a[i] > S[tail].v && tail >= head ) {
                ans += S[tail].same ;
                S[tail].v = S[tail].w = S[tail].same = S[tail].samA = 0 ;
                tail -- ;
            }
            if ( tail >= head && S[tail].v == a[i] ) {
                ans += S[tail].same ;
                S[tail].samA ++ ;
                Next[S[tail].Head] = i ;
                S[tail].Head = i ;
                if ( head < tail && S[tail-1].Head <= n ) ans ++ ;
                if ( i <= n ) S[tail].same ++ ;
            }
            else {
                if ( head <= tail && S[tail].Head <= n ) ans ++ ;
                tail ++ ;
                S[tail].v = a[i] ;
                S[tail].w = i ;
                S[tail].samA = 1 ;
                S[tail].Head = i ;
                if ( i <= n ) S[tail].same = 1 ;
            }
        }
        printf( "%lld\n" , ans ) ;
    }
    return 0 ;
}

以上.

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值