JZOJ4848. 【GDOI2017模拟11.3】永恒的契约 断环成链+单调栈

题目大意

给你个大小为 n 的环,环上的每个点上有一个高度ai,如果环上的两个位置 i,j 能看到,那么 min(ai,aj) 有大于等于两点间两条路径中其中一条的最大值。

n106
ai109

解题思路

方法一
首先环上的情况很难处理,那么考虑取走 ai 最大的位置,断环成链。因为我们取走的是最大值,所以不会有点对 (i,j) 以包含 i 点的路径作为判断是否能看到的依据,唯一的可能就是最大值有多个的时候,这种情况下,经过不包含i点的另一条路径显然也可以互相看到。这样,我们考虑完链上的贡献后,在累计删去的 i 点的贡献就可以了。

那么怎么统计链上的情况。为了不算重,考虑用右端点统计答案,问题转化为固定有右端点i时,有多少个左端点可以贡献答案。先考虑左边比 ai 小或等于的位置 j ,这是的min(ai,aj)对应的肯定是位置 j ,为了使i j 所有数都比aj小,那么对于左端点,肯定是单调递增的。在考虑左边比 aI 大的数,显然只有最靠近位置 i 的数能贡献。综上,我们只需维护一个递减的序列,每次位置i的贡献就是退栈元素的数量,如果有比 ai 大的点,再加1。 i 点的贡献很好求,直接正着扫一次,倒着扫一次,用单调栈判断合法解就行了。复杂度是O(n)的。

方法二:
一种奇怪的方法:
当然考试的时候我并不是这样想的,我的思想是,用容斥的思想,找总左边路径贡献答案的点对数加右边答案贡献的点对数,减去两边路径贡献的点对数就是答案。两边路径动能贡献的很好求,之间找出最大值和次大值统计一下就可以了。现在考虑统计左边路径贡献答案的个数,右边也一样。

不用破环成链,直接把环复制一遍,现在我们就是要找一个区间 [i,i+n] 内满足条件的对数,最后除2,就是贡献。对于一个位置 i ,显然右边第一个数是可以的,这个要特殊讨论一下。右边第一个大于aI的数肯定是边界。跟上面一样,我们也只需找到一个单调不减的序列,那么直接维护一个不减的序列,符合要求的个数就是 rl+1 。而某个位置 j 第一个大于aj的位置可以预处理,但现在问题是随着 i 向右走,可能边界会向左移这要二分来得到位置,但是这题的复杂度要求是O(n)的(虽然我加个log也过了)。那么我们考虑某个点右边第一个大于它的点,显然这只有一个,那么这就构成了一个树状的结构,对于一个 i 的贡献就是位置i+1的深度减边界左边符合要求最靠近点的深度。而这个点的位置可以用 lca 来求!而我们有知道一个 O(1) tarjan lca 的方法,这样复杂度就是 O(1) 了!

程序

方法一:

//ddddddpppppp
#include<cstdio>
#include<cstring>
#include<string>
#include<iostream>
#include<algorithm>
#include<cmath>

using namespace std;

#define fo(i,a,b) for (int i=a;i<=b;i++)
#define fd(i,a,b) for (int i=a;i>=b;i--)

typedef long long ll;

const int N=1100000;

int T,n,stack[N],tot[N],a[N],b[N],bz[N];

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

int main()
{
    scanf("%d",&T);
    while (T--)
    {
        scanf("%d",&n);
        int mx=0;
        fo(i,1,n) a[i]=read(),bz[i]=0,mx=max(mx,a[i]);
        fo(i,1,n)
            if (a[i]==mx)
            {
                fo(j,1,n-i) b[j]=a[j+i];
                fo(j,n-i+1,n-1) b[j]=a[j-n+i]; 
                break;
            }
        ll ans=0;
        stack[0]=0;
        fo(i,1,n-1)
        {
            while (stack[0]&&stack[stack[0]]<b[i])
            {
                ans+=tot[stack[0]];
                stack[0]--;
            }
            if (stack[0]&&stack[stack[0]]==b[i]) 
            {
                ans+=tot[stack[0]];
                if (stack[0]-1) ans++;
                tot[stack[0]]++;
            }
            else
            {
                if (stack[0]) ans++;
                stack[++stack[0]]=b[i];
                tot[stack[0]]=1;
            }
        }
        mx=0;
        fo(i,1,n-1)
            if (b[i]>=mx)
            {
                mx=b[i];
                if (!bz[i]) ans++;
                bz[i]=1;
            }
        mx=0;
        fd(i,n-1,1)
            if (b[i]>=mx)
            {
                mx=b[i];
                if (!bz[i]) ans++;
                bz[i]=1;
            }
        printf("%lld\n",ans);
    }
    return 0;
}

方法二(没加优化)

//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;
typedef long long LL;

const int MAXN = 2e6 + 5;

struct Node {
    int num, side;
    Node (int a, int b) {num = a, side = b;}
    Node () {}
};

Node f[MAXN * 4], d[MAXN];
int n, m, a[MAXN], l[MAXN], lg[MAXN], rmq[MAXN * 2][22], t1[MAXN], t2[MAXN];

void read(int &x) {
    char ch = getchar();
    while (ch < '0' || ch > '9') ch = getchar();
    x = 0;
    while (ch >= '0' && ch <= '9') x = x * 10 + ch - '0', ch = getchar();
}

void prepare() {
    int top = 0;
    memset(t1, 0, sizeof t1), memset(t2, 0, sizeof t2);
    for (int i = 1; i <= m; i ++) {
        while (top && a[i] >= d[top].num) t1[d[top --].side] = i;
        d[++ top] = Node(a[i], i);
    }
    top = 0;
    for (int i = 1; i <= m; i ++) {
        while (top && a[i] > d[top].num) t2[d[top --].side] = i;
        d[++ top] = Node(a[i], i);
    }
}

LL work() {
    prepare();
    int l = MAXN * 2 + 1, r = MAXN * 2;
    LL ans = 0;
    for (int i = 1; i <= n; i ++) {
        int lim = t2[i];
        if (lim == 0 || lim > i + n - 1) lim = i + n - 1;
        if (lim == i + 1) {
            ans ++;
            l = min(l + 1, r + 1);
            continue;
        }
        int side = i + 1;
        int top = 0;
        bool flag = 0;
        while (side != 0 && side <= lim) {
            if (!flag) {
                if (l <= r && f[l].side != side) {
                    d[++ top] = Node(a[side], side);
                    side = t1[side];
                } else {
                    if (l > r) f[++ r] = Node(a[side], side);
                    side = t1[f[r].side];
                    flag =  1;
                }
            } else {
                f[++ r] = Node(a[side], side);
                side = t1[side];
            }
        }
        for (int j = top; j; j --) f[-- l] = d[j];
        int lx = l, rx = r, add = 0;
        while (lx <= rx) {
            int mid = (lx + rx) >> 1;
            if (f[mid].side <= lim) add = mid, lx = mid + 1; else
                rx = mid - 1;
        }
        ans += (add - l + 1);
        l = min(l + 1, r + 1);
    }
    return ans;
}

void solve() {
    scanf("%d", &n);
    m = n;
    for (int i = 1; i <= n; i ++) read(a[i]);
    for (int i = 1; i <= n; i ++) a[++ m] = a[i];
    LL ans = work();
    for (int i = 1; i <= m / 2; i ++) swap(a[i], a[m - i + 1]);
    ans += work();
    int mx = 0;
    for (int i = 1; i <= n; i ++) mx = max(a[i], mx);
    int num = 0;
    for (int i = 1; i <= n; i ++) 
        if (a[i] == mx) num ++;
    ans /= 2;
    if (num > 1) ans -= 1ll * num * (num - 1) / 2; else {
        int mxx = 0;
        for (int i = 1; i <= n; i ++) {
            if (a[i] == mx) continue;
            mxx = max(a[i], mxx);
        }
        int numm = 0;
        for (int i = 1; i <= n; i ++) if (a[i] == mxx) numm ++;
        ans -= numm;
    }
    printf("%lld\n", ans);
}

int main() {
    int t;
    scanf("%d", &t);
    for (int i = 1; i <= t; i ++) solve();
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值