51Nod-1482 部落信号(单调栈)

本文介绍了一种解决特定环形数组问题的算法——如何计算一个长度为n的环上可互相看见的数对数量。核心思路是将环转换为链,并使用单调栈来找出每个元素左侧和右侧的第一个较大值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

一个长度为 nn 的环 a ,如果环上有一对数 x,yx,y,当连接 x,yx,y 的两对弧中任意一对上不存在比 min{x,y}min{x,y} 大的数,则称 x,yx,y 可以互相看见,问有多少对数可以互相看见。
3n1063≤n≤106

思路

首先,与环有关的问题,最基本的思路就是段环成链。因为连接 x,yx,y 的圆弧,一定不必穿过环上的最大值,所以可以去掉环上最大值并把环拆成链,最后重新算一遍最高点的贡献即可。不难发现,将 xx 作为较低点,它能连结的点有左边第一个比它大的点和右边第一个比它大的点,于是想到单调栈。但是这样有一个问题,如果单调栈中出现了相同元素,那么点 x 还能连接到所有与 xx 大小相同的点。所以还要特殊处理这一类点。单调栈进行的过程中始终保持序列的递减,那只用用 x 替换栈内元素时,维护一个 ss 数组如果遇到相同元素,则通过累加得到与 x 相同的元素个数即可。准确的说,是 xx 向左边拓展,在遇到比它大的点时停下,中途遇到与 x 相等的元素的个数(当然,除了将所有数的 ss 值加起来,也可以加上所有的 Ck2,其中 kk 为一个满足上述要求,值相等的点集中点的个数)。

代码

由于这题有些卡语言,所以用 C 语言交了

#include<stdio.h>
#define FOR(i,x,y) for(register int i=(x);i<=(y);++i)
#define DOR(i,x,y) for(register int i=(x);i>=(y);--i)
#define N 1000003
typedef long long LL;
int mark[N];
int a[N],t[N],s[N],n;
int stk[N],top;
int p,mx=-1,h;
LL ans;

int main()
{
    scanf("%d",&n);
    FOR(i,1,n)scanf("%d",&t[i]);
    FOR(i,1,n)if(t[i]>mx)mx=t[p=i];
    FOR(i,1,n-p)a[i]=t[i+p];
    FOR(i,n-p+1,n-1)a[i]=t[i-n+p];
    n--;
    a[0]=2e9,stk[top=0]=0;
    FOR(i,1,n)
    {
        while(a[i]>=a[stk[top]])
        {
            if(a[i]==a[stk[top]])s[i]=s[stk[top]]+1;
            top--;
        }
        if(top)ans++;
        stk[++top]=i;
    }
    a[n+1]=2e9,stk[top=0]=n+1;
    DOR(i,n,1)
    {
        while(a[i]>=a[stk[top]])top--;
        if(top)ans++;
        stk[++top]=i;
    }
    FOR(i,1,n)ans+=s[i];
    h=-1;
    FOR(i,1,n)
    {
        if(a[i]>=h)
        {
            h=a[i];
            if(!mark[i])
            {
                mark[i]=1;
                ans++;
            }
        }
    }
    h=-1;
    DOR(i,n,1)
    {
        if(a[i]>=h)
        {
            h=a[i];
            if(!mark[i])
            {
                mark[i]=1;
                ans++;
            }
        }
    }
    printf("%lld\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值