アルゴリズム改善ツリー配列


ツリー配列の 2 つの関数

  • 間隔の合計を素早く求める
  • 特定の数値をすばやく変更すると同時に、間隔や
  • tr[i] によって記録される間隔の長さは lowbit(i) です
    ここに画像の説明を挿入します

241. ロウラントーテム(インターバル合計+一点修正)

#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
typedef pair<int, int> pii;
const int N = 2e5 + 10;
int tr[N];
int a[N];
int n;
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)//统计下标为1-x的前缀和
{
    
    
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res;
}



void solve()
{
    
    
    scanf("%d", &n);
    vector<int> Greater(N+1);//y的取值正好也是1-n,否则就需要离散化了
    vector<int> lower(N+1);
    for (int i = 1; i <= n; ++ i) scanf("%d", &a[i]);
    for (int i = 1; i <= n; ++ i)
    {
    
    
        int y = a[i];
        Greater[i] = sum(n) - sum(y);//遍历到a[i]的时候,下标为(1, n)在n~y+1之间的数有几个
        lower[i] = sum(y-1);遍历到a[i]的时候,从1~y-1之间的数有几个
        add(y, 1);//遍历完a[i]后,将a[i]出现的次数加1,我们前缀和sum
    }

    memset(tr, 0, sizeof tr);
    
    typedef long long LL;
    LL res1 = 0, res2 = 0;
    for (int i = n; i; -- i)//逆向遍历
    {
    
    
        res1 += Greater[i] *(LL)(sum(n) - sum(a[i]));//记录下标在(n, i)之间有多少个数字大于a[i]
        res2 += lower[i] * (LL)(sum(a[i]-1));
        add(a[i], 1);
    }
    cout << res1 << " "<< res2;
}
int32_t main()
{
    
    
    ios::sync_with_stdio(0);
    cin.tie(0);
    int T = 1;
    // cin >> T;
    while (T --) solve();
    return 0;
}

242. 簡単な整数問題(差分+メンテナンス間隔修正を実現する押し公式+一点和)

ツリー配列は、単一点を加算してから区間の合計を求めるのが得意です。この質問は、区間を加算してから単一点の合計を求めることについてです。

  • 元の配列上の点の値を求める = 差分配列の接頭辞の合計を求める
  • 元の配列の間隔の値を変更する = 差分配列の 2 つの端点の
    値をの配列に対する操作を古典的なツリー配列操作 (単一点の値を変更して検索) に変換するにはここで、tr[] は a[] の差分配列を維持します。

差分配列 b[] と元の配列 a[] の関係
ここに画像の説明を挿入します

ここに画像の説明を挿入します
用树状数组实现前缀和 脱裤子放屁多此一举版本
実際、ツリー配列の機能を理解していなくても、この種のコードは作成できます。ツリー配列の機能は区間の合計と単一点の変更であるため、区間の合計と単一点の変更の複雑さはそれほど遅くありません。
配列の区間合計の計算量は n、単一点変更の計算量は 1、
プレフィックス合計の区間合計の計算量は 1、単一点変更の計算量は n、区間合計の計算量
と単一点変更の計算量は nツリー配列の -point 変更は次数がすべてですlogn
本题是区间修改和单点求和,这显然不是树状数组的正常操作,即我们树状数组不能去维护a[],而是应该去维护他的差分数组b[]

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <string.h>
#include <string>
#include <math.h>
#include <vector>
#include <queue>
#include <map>
using namespace std;
typedef pair<int, int> pii;
const int N = 1e5 + 10;
typedef long long LL;
int a[N];
LL tr[N];
int n, m;
// 10 5
// 1 2 3 4 5 6 7 8 9 10
// Q 4
// Q 1
// Q 2
// C 1 6 3
// Q 2
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;
}
LL presum(int x)
{
    
    
    LL res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res ;
}
void solve()
{
    
    
    cin >> n >> m;
    for (int i = 1; i <= n; ++ i) 
    {
    
    
        cin >> a[i];
        add(i, a[i]);
    }
    char op[2];
    while (m -- )
    {
    
    
        cin >> op;
        int l, r, x;
        if (*op == 'Q')
        {
    
    
            cin >> x;
            cout << presum(x) - presum(x - 1)<< endl;//这不就是前缀和么,虽然查找是1,但是修改就很大了,
        }                                           //我用树状数组多次一举的话更大
        else 
        {
    
    
            cin >> l >> r >> x;
            for(int i = l; i <= r; ++ i)//可以看到前缀和的话修改操作的复杂度是n^2logn
            {
    
    
                add(i, x);
            }
            
        }
    }

}
int32_t main()
{
    
    
    ios::sync_with_stdio(0);
    cin.tie(0);
    int T = 1;
    // cin >> T;
    while (T --) solve();
    return 0;
}

ACコード

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <string.h>
#include <string>
#include <math.h>
#include <vector>
#include <queue>
#include <map>
using namespace std;
typedef pair<int, int> pii;
const int N = 1e5 + 10;
typedef long long LL;
int a[N];
LL tr[N];
int n, m;
// 10 5
// 1 2 3 4 5 6 7 8 9 10
// Q 4
// Q 1
// Q 2
// C 1 6 3
// Q 2
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;
}
LL presum(int x)
{
    
    
    LL res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tr[i];
    return res ;
}
void solve()
{
    
    
    cin >> n >> m;
    for (int i = 1; i <= n; ++ i) 
    {
    
    
        cin >> a[i];
        add(i, a[i] - a[i - 1]);
    }
    char op[2];
    while (m -- )
    {
    
    
        cin >> op;
        int l, r, x;
        if (*op == 'Q')
        {
    
    
            cin >> x;
            cout << presum(x) << endl;//这不就是前缀和么,虽然查找是1,但是修改就很大了,
        }                                           //我用树状数组多次一举的话更大
        else 
        {
    
    
            cin >> l >> r >> x;
            add(l, x);add(r + 1, -x);
        }
    }

}
int32_t main()
{
    
    
    ios::sync_with_stdio(0);
    cin.tie(0);
    int T = 1;
    // cin >> T;
    while (T --) solve();
    return 0;
}

243. 簡単な整数問題2(区間修正と区間和)

  • 区間合計 + 単一点変更 = 通常のツリー配列 (プレフィックス合計と通常の配列の最適化)
  • 区間変更 + 単一点合計 = ツリー配列維持差分配列 (この場合、区間変更は差分配列の単一点変更に対応でき、単一点加算は差分配列の区間合計に対応できます)
  • 間隔の合計 + 間隔の変更 = 2 つのツリー配列を維持し、1 つは差分配列 b[i] (間隔によって変更可能) を維持し、もう 1 つは i*b[i] を維持します。この式を使用してペアを導出できます。 a[] 配列の間隔の合計は次の式ですが、
    ここに画像の説明を挿入します
    理解できない場合は、以下の説明を読んでください。
    ここに画像の説明を挿入します

ここに画像の説明を挿入します
もう 1 つの i * b[i] が維持されるため、add 関数の c は int を爆発させる可能性があるため、強制的に変更する必要があります

  • サンプルデータを渡してそのほとんどを渡したら、longlong がオンになっていないかを確認します。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <string.h>
#include <string>
#include <math.h>
#include <vector>
#include <queue>
#include <map>
using namespace std;
typedef pair<int, int> pii;
const int N = 1e5 + 10;
typedef long long LL;
int a[N];
LL tr[N], tri[N];
int n, m;

int lowbit(int x)
{
    
    
    return x & -x;
}
void add(LL tree[], int x, LL c)//开LL
{
    
    
    for (int i = x; i <= n; i += lowbit(i)) tree[i] += c;
}
LL presum(LL tree[], int x)
{
    
    
    LL res = 0;
    for (int i = x; i; i -= lowbit(i)) res += tree[i];
    return res ;
}
LL prefixsum (int x)
{
    
    
    return presum(tr, x) * (x + 1) - presum(tri, x);
}
void solve()
{
    
    
    cin >> n >> m;
    for (int i = 1; i <= n; ++ i) 
    {
    
    
        cin >> a[i];
        add(tr, i, a[i] - a[i - 1]);
        add(tri, i, (LL)i * (a[i] - a[i - 1]));//开LL
    }
    char op[2];
    while (m -- )
    {
    
    
        cin >> op;
        int l, r, x;
        if (*op == 'Q')
        {
    
    
            cin >> l >> r;
            cout << prefixsum(r) - prefixsum(l - 1) << endl;
        }
        else 
        {
    
    
            cin >> l >> r >> x;
            add(tr, l, x); add(tr, r + 1, -x);
            add(tri, l, l * x); add(tri, r + 1, (r + 1) * (-x));
        }
    }

}
int32_t main()
{
    
    
    ios::sync_with_stdio(0);
    cin.tie(0);
    int T = 1;
    // cin >> T;
    while (T --) solve();
    return 0;
}

AcWing 244. 不思議な牛 (二分探索を使用して目的の状態を見つける + ツリー配列で現在の間隔の状態を動的に記録する)

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <string.h>
#include <string>
#include <math.h>
#include <vector>
#include <queue>
#include <map>
using namespace std;
typedef pair<int, int> pii;
const int N = 1e5 + 10;
int tr[N], pos[N];
int n;
// 5
// 1
// 2
// 1
// 0
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 presum(int x)//树状数组的本质就是求前缀和
{
    
    
    int sum = 0;
    for (int i = x; i; i -= lowbit(i)) sum += tr[i];
    return sum;
}
int find(int k)//
{
    
    
    int l = 1, r = n;
    while (l < r)
    {
    
    
        int mid = l + r >> 1;
        if (presum(mid) >= k) r = mid;//求高度为1到n之间高度为mid的牛之前有多少头牛还没有被用过,其实也就是求高度为mid的牛在当前剩下的牛中是否为第k高的牛
        else l = mid + 1;           //因为我们求的是第一个第k高的牛,那么当前搜索的高度为mid的牛一定是还没有确定位置的牛
    }
    return l;
}
void solve()
{
    
    
    cin >> n;
    add(1, 1);
    for (int i = 2; i <= n; ++ i)
    {
    
    
        cin >> pos[i];
        add(i, 1);//单点修改tr[i] = 1,同时对i后面的树状数组造成影响。树状数组的本质就是前缀和,同时单点修改的复杂度也不低不高
    }
    vector<int> ans(n + 1);
    for (int i = n; i; -- i)
    {
    
    
        int height;
        height = find(pos[i] + 1);//找到当前在剩下的牛中第pos + i高的牛,它的高度是height
        ans[i] = height;
        add(height, -1);//表明这个height这个高度的牛已经用过了,他会对1-n中高度 >height的牛的前缀和造成影响
    }
    for (int i = 1; i <= n; ++ i) cout << ans[i] << endl;
}
int32_t main()
{
    
    
    ios::sync_with_stdio(0);
    cin.tie(0);
    int T = 1;
    // cin >> T;
    while (T --) solve();
    return 0;
}

おすすめ

転載: blog.csdn.net/chirou_/article/details/132118171