冷静 清醒 直视内心|

艾特玖

园龄:3年11个月粉丝:12关注:7

树状数组应用

树状数组应用

该栏目不断更新,各种树状数组题目

事情的起因是一道题目树上逆序对

看到题目的时候,就想到用树状数组去写,但是关于逆序对怎么用树状数组去写,记忆有些模糊了,就去翻了翻洛谷的板子题逆序对,然后惊奇的发现,原来写的方法,因为数据更新而被卡掉了。原来是用map做的离散化,因此时间复杂度是O(nlognlogn),就直接卡掉了。

因此,换了方法,想先将所有数字离散化好,再按照坐标顺序插入值(即在对应值处++),到第i个时候,逆序对为i-比a[i]小的数字个数。本来到这里就结束了。

但是再转身写树上逆序对的时候,不行。

我最开始的想法是

错误思路

枚举每一个点i,然后枚举分别从1-n-1叉树定义下的子节点区间(题目中也给了)。但是此时问题出现了,当我按坐标顺序插入值的时候,我想要知道的却是i后边的区间内,比a[i]小的节点个数。这不就尴尬了嘛,我还没到后面呢,怎么知道呢?

感觉要不行了,但是灵光乍现,突然想到之前写过的一道离线处理的一道树状数组题目数数,下面来说说延伸得到的思路。

正解思路

数数这道题目的思路就是,按照Hi值从小到大的顺序插入坐标(即在对应坐标处++),此时想要知道区间内,满足小于等于Hi条件的点,直接query(r) - query(l-1)即可,由此得到灵感。我们也可以来解决这道题。

我们可以预先将所有点按照点权的从小到大的顺序插入坐标,每次我们插入一个点i的时候,我们可以枚举对应的1 -> n-1叉树中,I的子节点区间,此时直接减一下就可以得到在k叉树中,该节点能提供的逆序对数量。

到这里,我们可以发现并总结树状数组操作的两种方式

我们再次强调一下。

我们想到用树状数组的关键词,对坐标和值同时进行限制,要立马想到树状数组

两种方式

按照值进行插入坐标

即按照下标建立树状数组

当对值与坐标均进行限制,对值的限制并不复杂

而以值建立时,需要离散化,那我们就用这种方式

否则,若可以直接以值建立会简单一些

例如,i前,比ai小的数的个数,就是经典应用。

这里有一个细节问题,是否去重。

或者说,我们判断的值是否是严格大于或小于。

我们还拿刚刚那个举例子。

i前,小于ai的数的个数。

i前,小于等于ai的数的个数。

那如何解决呢?注意:只是以上面两个问题举例,其他相似情况自行推导一下

我们发现,只需在排序的时候,对第一个问题

我们只需要在排序时,对相等的数,按照编号从大到小排序。这样再算的时候,直接sum(n)-sum(i)即可。

而对于第二个问题

我们只需要在排序时,对相等的数,按照编号从小到大排序。这样再算的时候,直接sum(n)-sum(i)即可。

逆序对

P1908 逆序对

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5e5 + 10;
pair<int,int> a[N];
int tr[N];
LL ans;
int n;

void add(int x,int c)
{
    while(x<=n)
    {
        tr[x] += c;
        x += x & -x;
    }
}

int sum(int x)
{
    int res = 0;
    while(x)
    {
        res += tr[x];
        x -= x & -x;
    }
    return res;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i].first),a[i].second = i;
    sort(a+1,a+n+1,greater<pair<int,int>>());
    for(int i=1;i<=n;i++)
    {
        add(a[i].second,1);
        ans+=sum(a[i].second-1);
    }
    printf("%lld",ans);
    return 0;
}

数数

#464. 数数

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
typedef pair<int,PII> PIII;
const int N = 1e5 + 10;
int tr[N];
int n,m;

struct Node
{
    int id,l,r,num;
    bool operator<(const Node &w) const
    {
        return num < w.num;
    }
};

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

void add(int x,int c)
{
    for(int i=x;i<=n;i+=lowbit(i))
        tr[i] += c;
}

int sum(int x)
{
    int res = 0;
    for(int i=x;i;i-=lowbit(i))
        res += tr[i];
    return res;
}

int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        vector<PII> a(n);
        vector<Node> b(m);
        vector<int> ans(m+1);
        for(int i=0;i<n;i++) 
        {
            scanf("%d",&a[i].first);
            a[i].second = i + 1;
        }
        for(int i=0;i<m;i++)
        {
            int l,r,v;scanf("%d%d%d",&l,&r,&v);
            b[i] = {i+1,l,r,v};
        }
        sort(a.begin(),a.end());
        sort(b.begin(),b.end());
        int k = 0;
        for(int i=0;i<m;i++)
        {
            while(k<n&&a[k].first<=b[i].num)
            {
                add(a[k].second,1);
                k++;
            }
            ans[b[i].id]=sum(b[i].r)-sum(b[i].l-1);
        }
        for(int i=1;i<=m;i++)
            cout<<ans[i]<<" ";
        cout<<"\n";
        for(int i=1;i<=n;i++) tr[i] = 0;
    }
    return 0;
}

树上逆序对

#559. 树上逆序对

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
pair<int,int> a[N];
int tr[N];
LL ans[N];
int n;

void add(int x,int c)
{
    while(x<=n)
    {
        tr[x] += c;
        x += x & -x;
    }
}

int sum(int x)
{
    int res = 0;
    while(x)
    {
        res += tr[x];
        x -= x & -x;
    }
    return res;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i].first),a[i].second = i;
    sort(a+1,a+n+1);
    for ( int i = 1;i <= n;i++ ) {
        for ( int j = 1;j <= n - 1;j++ ) {
            if ( j * (a[i].second - 1) + 2 > n )
                break;
            ans[j] += sum(min(j * a[i].second + 1, n)) - sum(j * (a[i].second - 1) + 1);
        }
        add(a[i].second,1);
    }
    for(int i=1;i<=n-1;i++)
        printf("%lld ",ans[i]);
    return 0;
}

HH的项链(区间去重)

P1972 [SDOI2009]HH的项链

历史(刷题)的经验告诉我们,处理区间去重时,我们往往会离线操作。

我们将区间按照r从小到大排序,接下来

按照顺序将所有的区间遍历到,同时不断的填充区间内的数。

此时若,该位置的值之前已经出现过,那么我们就将之前的删除,并在该位置插入该数。

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
struct Node
{
    int l,r;
    int id;
    bool operator<(Node &W)const 
    {
        return r<W.r;
    }
}Line[N];
int tr[N];
int w[N],pos[N],ans[N];
int n,m;

void add(int x,int c)
{
    while(x<=n)
    {
        tr[x] += c;
        x += x & -x;
    }
}

int sum(int x)
{
    int res = 0;
    while(x)
    {
        res += tr[x];
        x -= x & -x;
    }
    return res;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        int l,r;scanf("%d%d",&l,&r);
        Line[i] = {l,r,i};
    }
    sort(Line+1,Line+m+1);
    int idx = 1;
    for(int i=1;i<=m;i++)
    {
        for(int j=idx;j<=Line[i].r;j++)
        {
            if(pos[w[j]]) add(pos[w[j]],-1);
            pos[w[j]] = j;
            add(j,1);
        }
        idx = Line[i].r + 1;
        ans[Line[i].id] = sum(Line[i].r) - sum(Line[i].l-1);
    }
    for(int i=1;i<=m;i++) cout<<ans[i]<<endl;
    return 0;
}

合适数对

4316. 合适数对

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
struct Node
{
    LL w,id;
    bool f;
    bool operator<(const Node& W) const
    {
        if(w==W.w) return id<W.id;
        return w<W.w;
    }
}a[N<<1];
int tr[N];
LL n,t;

void add(int x,int c)
{
    while(x<=n)
    {
        tr[x] += c;
        x += x & -x;
    }
}

LL sum(int x)
{
    LL res = 0;
    while(x)
    {
        res += tr[x];
        x -= x & -x;      
    }
    return res;
}

int main()
{
    cin>>n>>t;
    LL res = 0;
    LL ans = 0;
    int cnt = 0;
    for(int i=1;i<=n;i++)
    {
        LL x;cin>>x;
        res += x;
        if(res<t) ans++;
        a[cnt].w = res - t,a[cnt++].id = i;
        a[cnt].w = res,a[cnt].id = i,a[cnt++].f = 1;
    }
    sort(a,a+cnt);
    for(int i=0;i<cnt;i++)
    {
        if(a[i].f) ans += sum(n) - sum(a[i].id);
        else add(a[i].id,1);
    }
    cout<<ans<<endl;
    return 0;
}

按照坐标进行插入值

其实就是按照值域建树状数组

当对值与坐标均进行限制,但是对于某一个数,我们对其值的限制有大于等于两个时。

P2184 贪婪大陆

可以直接看看我的题解。

这里面,我们对值的限制就很奇怪,因此,我们只能以值域建立树状数组。

本文作者:艾特玖

本文链接:https://www.cnblogs.com/aitejiu/p/16023719.html

版权声明:本作品采用艾特玖许可协议进行许可。

posted @   艾特玖  阅读(37)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起