永恒的契约

Description

宅邸迅速的燃烧着,必须带贝蒂走出禁书库!凭着感觉,又一次直接找到禁书库的门。
“你,是那个人嘛?”400年了,当初圣域建立结界时没有进入圣域,被伤了心的人工精灵贝蒂,与强欲魔女签订契约,守护宅邸的禁书库,直至“那个人”的到来,那个人会解开贝蒂的心结。
“我不是那个什么人,但我会成为你唯一的人。我会给你幸福!”
精灵与人签订契约,从此相依为命。这便是,永恒的契约。

宅邸里,罗兹瓦尔的房间图书柜后,有一条链接宅邸和圣域的秘密通道,其中有一个神奇的大回环,由  n    n   块石头组成。
第i块石头有一个高度  ai    a i   ,两块不同的石头i,j能够互相看到,则它们在环上的两条路径中有至少一条路径上除了两个端点(即  i,j    i , j   )路径上石头高度都不大于 min(ai,aj) m i n ( a i , a j )
被罗兹瓦尔雇佣的猎肠者躲在这秘密的通道中,为了能够更好的观察通道中的情况,她想知道有多少对石头能够互相看到。

Input

第一行一个正整数  T    T   ,表示数据组数。
接下来  T    T   组数据,每组数据第一行读入正整数  n    n   ,接下来一行按顺时针顺序读入序列  a    a   表示石块的高度。

Output

 T    T   行表示每组数据的答案。

Sample Input

1
5
1 2 4 5 3

Sample Output

7

Data Constraint

40%n200 40 % , n ≤ 200
60%n2000 60 % , n ≤ 2000
70%n100000 70 % , n ≤ 100000
80%n1000000,1ai1000000 80 % , n ≤ 1000000 , 1 ≤ a i ≤ 1000000
100%n1000000T51ai1000000000 100 % , n ≤ 1000000 , T ≤ 5 , 1 ≤ a i ≤ 1000000000

solution

首先对于像这种对于环的题目一般首先会有这样的思路:
①将这段序列复制一段
②选一个作“起点”,该起点的作用就是可以断绝前后关系。

比如这道题,我们就选最大的  ai    a i   作起点,若存在多个最大点则任选一个,不影响。
接下来就谈谈做法吧。
我们设  a1    a 1   为最大值,那么该序列就变为  a1,a2,a3,...an    a 1 , a 2 , a 3 , . . . a n   ,另外注意在最后再加一个  an+1=a1    a n + 1 = a 1   ,表示首尾相接。

我们假定 i<j i < j ,那么我们现在要求所有  ai<aj    a i < a j   且能互相看见的对数。

 ai<aj    a i < a j   易得  a[i+1..j1]ai   ∀ a [ i + 1.. j − 1 ] ≤ a i ,所以能与  ai    a i   匹配的  aj    a j   是第一个比  ai    a i   大的数,即存在唯一性!
我们设这个  aj    a j    next[i]   n e x t [ i ]
所以除了  ai=a1    a i = a 1    ai    a i   外(即为该数亦为最大值),其实全部的  ai    a i   都有与之对应的  next[i]   n e x t [ i ] ,可以线性统计答案,具体来讲就是单调栈维护不上升序列
但是为了保证不算重,所以我们要将那些 aj=an+1 a j = a n + 1 (即  a1    a 1   ) 的数打上  tag   t a g ,然后我们倒过来再做一遍统计答案,遇到带有  tag    t a g    next[i]=a1    n e x t [ i ] = a 1   的就不必算入答案内。

那么 ai=aj a i = a j 的情况呢

显然我们也可以在单调栈中统计连续的数出现的次数。
神马?还不懂?如果出现次数是  z    z   ,那不就有 z(z1)/2 z ∗ ( z − 1 ) / 2 吗?

code

#include<cstdio> 
#define maxn 1000005
using namespace std;
int a[2*maxn],dt[2*maxn],p[2*maxn];
long long ans,z,d;
int st,t,n,i,j,ma,t2;
int main(){
    freopen("forever.in","r",stdin);
    freopen("forever.out","w",stdout);
    scanf("%d",&t);
    for (int t2=1;t2<=t;t2++){
        scanf("%d",&n);     
        ans=0;ma=0;
        for (int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            a[i+n]=a[i];
            if (a[i]>ma){
                st=i;
                ma=a[i];
            }
        }
        d=1;dt[d]=st;
        for (int i=st+1;i<=st+n;i++){
            p[i]=0;
            if (a[i]>a[i-1])
                while ((d>0)&&(a[i]>a[dt[d]]) ){
                    ans++;
                    if (i==st+n) p[dt[d]]=1;
                    if (a[dt[d]]==a[dt[d+1]]) z++; else{
                        ans+=z*(z+1)/2;
                        z=0;
                    }
                    d--;
                }
            ans+=z*(z+1)/2;z=0;
            dt[++d]=i;dt[d+1]=0;
        }
        ans+=(d-1)*(d-2)/2;
        d=1;dt[d]=st+n;
        for (int i=st+n-1;i>=st;i--){
            if (a[i]>a[i+1]) while ((d>0)&&(a[i]>a[dt[d]])){
                if ((i!=st) || ((i==st)&&(!p[dt[d]])))ans++;
                d--;
            } 
            dt[++d]=i;dt[d+1]=0;
        }
        printf("%lld\n",ans);
    }
    fclose(stdin);
    fclose(stdout);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值