【jzoj4848】【永恒的契约】【单调栈】

题目大意

有一个环,由n块石头组成。第i块石头有一个高度ai,两块不同的石头i,j能够互相看到,则它们在环上的两条路径中有至少一条路径上除了两个端点(即i,j)路径上石头高度都不大于min(ai,aj)。求有多少对石头能够互相看到。

解题思路

假如在序列上,合法的i,j(i小于j,ai小于aj)一定是i往右第一个比它大的数,这个可以用单调栈做。对于ai==aj一样可以用单调栈求出中间没有比它们大的ai==aj个数。对于环可以在最大值处破开去掉最大值,只有同是最大值才有可能越过破开的点,然而根本不需要,因ai,aj也是最大值。对于破开的点的贡献,一个点往左或往右没有一个比它大的数即可以贡献。

code

#include<set>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
#define LD double
#define max(a,b) ((a>b)?a:b)
#define min(a,b) ((a>b)?b:a)
#define fo(i,j,k) for(LL i=j;i<=k;i++)
#define fd(i,j,k) for(LL i=j;i>=k;i--)
using namespace std;
LL const inf=2147483647;
LL const maxn=1000000;
LL n,t,v,a[maxn+10],b[maxn+10],top,st[maxn+10],lef[maxn+10],righ[maxn+10],s[maxn+10];
char ch;
LL get(){
    v=0;ch=getchar();
    for(;(ch<'0')||(ch>'9');ch=getchar());
    for(;(ch>='0')&&(ch<='9');v=v*10+ch-'0',ch=getchar());
    return v;
}
int main(){
    //freopen("forever.in","r",stdin);
    //freopen("forever.out","w",stdout);
    freopen("d.in","r",stdin);
    freopen("d.out","w",stdout);
    scanf("%lld",&t);
    fo(cas,1,t){
        scanf("%lld",&n);
        LL mx=0,pos,ans=0,tmp=0;
        fo(i,1,n){
            a[i]=get();
            if(a[i]>mx){
                mx=a[i];
                pos=i;
            }
        }
        fo(i,1,n-1)
            b[i]=a[(pos+i>n)?pos+i-n:pos+i];
        top=0;
        fo(i,1,n-1){
            tmp=0;
            for(;top&&(b[st[top]]<=b[i]);ans+=(b[st[top]]==b[i])*s[top],tmp+=(b[st[top]]==b[i])*s[top],top--);
            if(top){
                lef[i]=st[top];
                ans++;
            }
            else lef[i]=0;
            st[++top]=i;s[top]=1+tmp;
        }
        top=0;
        fd(i,n-1,1){
            for(;top&&(b[st[top]]<=b[i]);top--);
            if(top){
                righ[i]=st[top];
                ans++;
            }
            else righ[i]=0;
            st[++top]=i;
        }
        fo(i,1,n-1)
            if((!lef[i])||(!righ[i]))ans++;
        printf("%lld\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值