首页 > 其他分享 >洛谷题单指南-线段树-P1637 三元上升子序列

洛谷题单指南-线段树-P1637 三元上升子序列

时间:2024-12-06 17:44:45浏览次数:13  
标签:数组 int 洛谷题 线段 个数 sum1 P1637 序列 三元

原题链接:https://www.luogu.com.cn/problem/P1637

题意解读:统计序列a[1]~a[n]中三元上升子序列的个数,三元上升子序列是指对于1<=i<j<k<=n有a[i]<a[j]<a[k],(a[i],a[j],a[k])成为一组上升子序列。

解题思路:

1、先思考一下暴力,通过三重循环枚举i,j,k找到所有i<j<k时符合a[i]<a[j]<a[k]的数量即可,显然会超时。

2、再进一步思考,要统计所有三元上升子序列的个数,对于每一个a[i],只要知道a[1]~a[i-1]中<a[i]的数组成的所有二元上升子序列的个数,在每个二元上升子序列后追加一个a[i],即可得到以a[i]结尾的三元上升子序列的个数,把以所有a[1]~a[n]结尾的三元上升子序列的个数加起来就是答案。

3、那么问题就转化成,对于一个a[i],如何统计a[1]~a[i-1]中<=a[i]的所有二元上升子序列的个数?

只需要计算a[1]~a[i-1]中所有以<=a[i]的数结尾的二元上升子序列个数,求和即可。

设s[i]表示以i结尾的的所有二元上升子序列的个数,sum1[i]表示1~i的个数

那么可以分析得到:s[i] = sum[i-1],考虑到元素可能重复,所以有:s[i] += sum1[i-1]

这样一来,在计算以a[i]结尾的三元上升子序列个数时,只需s[1]+s[2]+...+s[a[i]-1],

因此需要维护s的前缀和,设为sum2[i]表示s[1]+...+s[i]

4、要快速计算sum1[i],可以定义一个h[i]表示元素i的个数,这样sum[i] = h[1] + ... + h[i],sum就是h的前缀和数组

5、通过遍历a[1]、a[2]...a[n],对于每一个a[i],将sum2[a[i]-1]累加到答案,并记录其个数h[a[i]]++,这样就能够实现h数组的动态更新,边更新h[a[i]],边计算sum1[a[i]],再更新s[a[i]],累加即得答案。

因此,本题的关键就在于对h数组和s数组进行单点修改以及区间查询,通过树状数组或者线段树都可以解决。

这里,采用两个树状数组来分别维护h和s。

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 100005; //注意a的范围是1~100000
int tr1[N], tr2[N], a[N]; //tr1对应h,tr2对应s
int n;
long long ans;

int lowbit(int x)
{
    return x & -x;
}

void add1(int x, int val)
{
    //注意i取值最大不是n,而是N
    for(int i = x; i <= N; i += lowbit(i)) tr1[i] += val; 
}

int sum1(int x)
{
    int res = 0;
    for(int i = x; i != 0; i -= lowbit(i)) res += tr1[i];
    return res;
}

void add2(int x, int val)
{
    //注意i取值最大不是n,而是N
    for(int i = x; i <= N; i += lowbit(i)) tr2[i] += val; 
}

int sum2(int x)
{
    int res = 0;
    for(int i = x; i != 0; i -= lowbit(i)) res += tr2[i];
    return res;
}

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int i = 1; i <= n; i++)
    {
        ans += sum2(a[i] - 1); //以a[i]结尾的三元上升子序列的个数=所有小于a[i]结尾的二元上升子序列个数之和
        add2(a[i], sum1(a[i] - 1)); //更新以a[i]结尾的二元上升子序列个数=所有小于a[i]的个数
        add1(a[i], 1); //更新a[i]的个数
    }
    cout << ans;
    return 0;
}

 

标签:数组,int,洛谷题,线段,个数,sum1,P1637,序列,三元
From: https://www.cnblogs.com/jcwy/p/18589087

相关文章

  • 懒标记线段树
    点击查看代码//改build,apply,operator+//奇奇怪怪但能用structTag{lladd=0;voidapply(Tag&t){add+=t.add;return;}};structInfo{llsum=0;voidapply(Tag&t,intl,intr){sum+=t.add*(r-......
  • 洛谷题单指南-线段树-P6492 [COCI2010-2011#6] STEP
    原题链接:https://www.luogu.com.cn/problem/P6492题意解读:一个序列,初始L,可以指定一个位置修改,L修改成R,R修改成L,可以令L=0,R=1,然后每次修改后输出序列最长不连续0、1(0/1交替出现)的长度。解题思路:序列支持单点修改(0->1,1->0),区间查询(最长不连续0、1长度),因此可以采用线段树,不需要懒标......
  • 洛谷题单指南-线段树-P1471 方差
    原题链接:https://www.luogu.com.cn/problem/P1471题意解读:给定序列a[n],支持三种操作:1.将区间每个数加上一个数2.查询区间的平均数3、查询区间的方差解题思路:要支持区间修改和查询,首选线段树,下面看线段树节点需要维护的信息平均数=区间和/n,所以第一个要维护的信息是区间和......
  • 线段树维护最大子段和及其类似问题
    引入link。我们可以分析出上题就是带修改的最大子段和。遇到这种类型的题目应该想到用线段树。实现对于原数列,先建起一棵线段树,每个节点包含最大前缀、最大后缀、最大字段和、区间和信息。当你明确一道题是线段树时,要先思考pushup和pushdown怎么写,因为剩下的都是差不......
  • 动态开点线段树
    概况背景线段树,作为一种高级数据结构,其时间复杂度十分优秀。但有一个问题,就是需要的空间非常大,比如,现在给你一个长度为1e9的数组,让你在上面建立线段树。这种情况下连数组都开不了,更何况需要四倍空间的线段树呢。动态开点线段树就在这样的情况下出现了。思路顾名思义,动态开点......
  • 权值线段树
    线段树这种数据结构一般用于区间操作的题目之中,比如区间修改或者区间查询,但当我们想要针对值的数量等信息时,就会用到权值线段树,其本质就是建立在桶上的线段树。其代码和普通线段树没有什么区别,但其功能有些不同:1.查询某个元素的排名:点击查看代码#definelsid<<1#definers......
  • 洛谷题单指南-线段树-P4513 小白逛公园
    原题链接:https://www.luogu.com.cn/problem/P4513题意解读:给定序列a[n],支持两种操作:1.查询区间[l,r]内的最大子段和2.将a[x]修改成s,输出其中每一个查询操作的结果。解题思路:区间问题依然想到线段树,问题主要在于线段树的节点要维护哪些信息:最直接的,肯定要维护节点所表示区间的......
  • 线段树学习笔记
    线段树学习笔记对线段树的介绍线段树是一种树形数据结构,属于二叉树一棵线段树的每个结点都可以用区间\([l,r]\)来表示。线段树的叶结点表示的区间为\([l,l]\)或\([r,r]\)。线段树支持单点修改,区间修改,区间和/区间最值等的查询。对于线段树的区间修改一般采用延迟修改方式线......
  • 非递归线段树实现
    ZKW非递归线段树参考文章:线段树详解(非递归版)_非递归线段树-CSDN博客建树:原数组[1,n]存在线段树的[2,n+1](为了方便区间查询,要空出第一个节点和最后一个节点)区间查询若查询[L,R]区间,选取L-1和R+1两个节点,向上寻找.每次到达父节点L-1查看自己的父亲的右节点......
  • 洛谷题单指南-线段树-P3373 【模板】线段树 2
    原题链接:https://www.luogu.com.cn/problem/P3373题意解读:对于序列a[n],支持三种操作:1.对区间每个数乘上一个数2.对区间每个数加上一个数3.求区间和解题思路:由于支持乘、加两种区间修改操作,是线段树的另一种典型应用:多个懒标记显然,这里需要两个懒标记,mul表示对子节点区间每个......