線段樹和樹狀數組 (筆記)

单点修改和区间求和的模板

luogu P3374

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<queue>
#include<map>
#define MOD 1000000007
using namespace std;
typedef long long ll;
int n, m;
int a[1000000];
int v[4000000];
void build(int x, int l, int r){
	if(l == r){
		v[x] = a[l];
		return;
	}
	int mid = (l + r) >> 1;
	build(x<<1, l, mid);
	build(x<<1|1, mid + 1, r);
	v[x] = v[x<<1] + v[x<<1|1];
}
//y is the node you want to modify
void modify(int x, int l, int r, int y, int z){
	if(l == r){
		v[x] += z;
		return ;
	}
	int mid = (l + r)>>1;
	if(y <= mid){
		modify(x<<1, l, mid, y, z);
	}
	else{
		modify(x<<1|1, mid+1, r, y, z);
	}
	v[x] = v[x<<1]+v[x<<1|1];
}
ll query(int x, int l, int r, int ql, int qr){
	if(l == ql && r == qr){
		return v[x];
	}
	int mid = (l + r)>>1;
	if(ql <= mid && qr>mid){
		return query(x<<1, l, mid, ql, mid) + 
				query(x<<1|1, mid + 1, r, mid + 1, qr);
	}
	else if(qr <= mid){
		return query(x<<1, l, mid, ql, qr);
	}
	else{
		return query(x<<1|1, mid + 1, r, ql, qr);
	}
}
int main(){
	cin >> n >> m;
	for(int i = 1; i <= n; i++){
		cin >> a[i];
	}
	build(1, 1, n);
	int l, r, k;
	while(m--){
		cin >> k >> l >> r;
		if(k == 1){
			modify(1, 1, n, l, r);
		}else{
			cout << query(1, 1, n, l, r) << endl;
		}
	}
	return 0;
}

也可以用来求LCA.

leetcode 236 

class Solution {
public:
    map<int, TreeNode*> a;
    int atop = 0;
    map<TreeNode*, int> b;
    map<TreeNode*, int> dp;
    TreeNode* v[610000];
    void dfs(TreeNode* root){
	    if(!root)return;
        a[++atop] = root;
        b[root] = atop;
        if(root->left){
            dp[root->left] = dp[root] + 1;
            dfs(root->left);
            a[++atop] = root;
        }
        if(root->right){
            dp[root->right] = dp[root] + 1;
            dfs(root->right);
            a[++atop] = root;            
        }
    }
    TreeNode* cmp(TreeNode* x, TreeNode* y){
        if(dp[x] < dp[y])return x;
        return y;
    }
    void build(int x, int l, int r){
        if(l == r){
            v[x] = a[l];
            return;
        }
        int mid = (l + r) >> 1;
        build(x<<1, l, mid);
        build(x<<1|1, mid + 1, r);
        v[x] = cmp(v[x<<1], v[x<<1|1]);
    }
    TreeNode* query(int x, int l, int r, int ql, int qr){
        if(l == ql && r==qr){
            return v[x];
        }
        int mid = (l + r) >> 1;
        if(ql <= mid && mid < qr){
            return cmp(query(x<<1, l, mid, ql, mid),
                      query(x<<1|1, mid+1, r, mid + 1, qr));
        }
        else if(qr <= mid){
            return query(x<<1,l,mid, ql, qr);
        }
        else{
            return query(x<<1|1, mid+1, r, ql, qr);
        }
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        dfs(root);
        build(1, 1, atop);
        if(b[p] > b[q]){
            return query(1, 1, atop, b[q], b[p]);
        }else{
            return query(1, 1, atop, b[p], b[q]);            
        }
    
    }
};

luogu P3379 求LCA 

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<queue>
#include<map>
#define MOD 1000000007
using namespace std;
typedef long long ll;
int n, m, o;
struct edge{
	int next, y;
}e[1100000];
int link[510000], ltop = 0;
void insert(int x, int y){
	e[++ltop]= (edge){link[x], y};
	link[x] = ltop;
	e[++ltop] = (edge){link[y], x};
	link[y] = ltop;
}
int a[1100000], atop = 0;
int b[510000];
int dp[510000];
int v[4100000];
void dfs(int x, int y){
	a[++atop] = x;
	b[x] = atop;
	for(int i = link[x]; i; i=e[i].next){
		if(e[i].y == y)continue;
		dp[e[i].y] = dp[x] + 1;
		dfs(e[i].y, x);
		a[++atop] = x;
	}
}
int cmp(int x, int y){
	if(dp[x] < dp[y])return x;
	return y;
}
void build(int x, int l, int r){
	if(l == r){
		v[x] = a[l];
		return ;
	}
	int mid = (l + r) >> 1;
	build(x<<1, l, mid);
	build(x<<1|1, mid + 1, r);
	v[x] = cmp(v[x<<1], v[x<<1|1]);
}
int query(int x, int l, int r, int ql, int qr){
	if(l == ql && r == qr){
		return v[x];
	}
	int mid = (l + r) >> 1;
	if(ql <= mid && mid < qr){
		return cmp(query(x<<1, l, mid, ql, mid),
					query(x<<1|1, mid + 1, r, mid + 1, qr));
	}
	else if(qr<=mid){
		return query(x<<1, l, mid, ql, qr);
	}
	else{
		return query(x<<1|1, mid + 1, r, ql, qr);
	}
}
int main(){
	ios_base::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	
	cin >> n >> m >> o;
	int l, r;
	for(int i = 1; i < n; i++){
		cin >> l >> r;
		insert(l, r);
	}
	dfs(o, 0);
	build(1, 1, atop);
	while(m-- > 0){
		cin >> l >> r;
		if(b[l] > b[r]){
			swap(l, r);
		}
		cout << query(1, 1, atop, b[l], b[r]) << endl;
	}
	return 0;
}

整体区间修改.

P3373

这一题有乘法修改和加法修改, 要弄两个懒标签数组, 一个应对乘法, 一个应对加法.

当有乘法的修改时, 我们要把加法的懒标签一并变大同等倍数, 但是加法操作的时候不需要帮乘法懒标签加上, 因为乘法标签只记录倍数, 而这个倍数对加法操作有影响.

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<queue>
#include<map>
using namespace std;
typedef long long ll;
ll MOD;
int n, m;
vector<ll> a(110000, 0);
vector<ll> v(410000, 0);
vector<ll> d1(410000, 1);
vector<ll> d2(410000, 0);
void build(int x, int l, int r){
	if(l == r){
		v[x] = a[l];
		return;
	}
	int mid = (l + r)>>1;
	build(x<<1, l, mid);
	build(x<<1|1, mid + 1, r);
	v[x] = (v[x<<1] + v[x<<1|1]) % MOD;
}
void mutiplyd(int x, ll z){
	d1[x] = d1[x] * z % MOD;
	d2[x] = d2[x] * z % MOD;
	v[x] = v[x] * z % MOD;
}
void plusd(int x, int l, int r, ll z){
	d2[x] = (d2[x] + z) % MOD;
	v[x] = (v[x] + z * (r - l + 1)) % MOD;
}
void pushdown(int x, int l, int r, int mid){
	if(d1[x] != 1){
		mutiplyd(x<<1, d1[x]);
		mutiplyd(x<<1|1, d1[x]);
		d1[x] = 1;
	}
	if(d2[x] != 0){
		plusd(x<<1, l, mid, d2[x]);
		plusd(x<<1|1, mid + 1, r, d2[x]);
		d2[x] = 0;
	}
}
//y is the operation 1 is mutiply 2 is add
void modify(int x, int l, int r, int ql, int qr, int y, ll z){
	if(l == ql && r == qr){
		if(y == 1){
			mutiplyd(x, z);
		}else{
			plusd(x, l, r, z);
		}
		return;
	}
	int mid = (l + r)>>1;
	pushdown(x, l, r, mid);
	if(ql <= mid && mid < qr){
		modify(x<<1, l, mid, ql, mid, y, z);
		modify(x<<1|1, mid+1, r, mid+1, qr, y, z);
	}
	else if(qr <= mid){
		modify(x<<1, l, mid, ql, qr, y, z);
	}else{
		modify(x<<1|1, mid+1, r, ql, qr, y, z);
	}
	v[x] = (v[x<<1]+v[x<<1|1]) % MOD;
}
ll query(int x, int l, int r, int ql, int qr){
	if(l == ql && r == qr){
		return v[x];
	}
	int mid = (l + r)>>1;
	pushdown(x, l, r, mid);
	if(ql <= mid && qr>mid){
		return (query(x<<1, l, mid, ql, mid) + 
				query(x<<1|1, mid + 1, r, mid + 1, qr))%MOD;
	}
	else if(qr <= mid){
		return query(x<<1, l, mid, ql, qr);
	}
	else{
		return query(x<<1|1, mid + 1, r, ql, qr);
	}
}
int main(){
	cin >> n >> m >> MOD;
	for(int i = 1; i <= n; i++){
		cin >> a[i];
	}
	build(1, 1, n);
	int l, r, k;
	while(m-- > 0){
		int oo;
		cin >> oo;
		if(oo == 1){
			cin >> l >> r >> k;
			modify(1, 1, n, l, r, 1, k);
		}else if(oo == 2){
			cin >> l >> r >> k;
			modify(1, 1, n, l, r, 2, k);
		}else{
			cin >> l >> r;
			cout << query(1, 1, n, l, r) << endl;
		}
	}
	return 0;
}

树状数组

不用理解原理, 背了就行.

单点修改, 区间求和

int v[100000];
int lowbit(int x){return x&-x;}
int query(int x){
    int sum = 0;
    for(int i = x; i; i-= lowbit(i)){
        sum+=v[i];
    }
    return sum; 
}
void modify(int x, int z){
    for(int i = x; i <= n; i+= lowbit(i)){
        v[i] += y;
    }
}

权值线段树求逆序对

什么是权值线段树呢?

就是考虑新开一些数组, 然后透过线段树对他们进行维护.

对于逆序对, 我们只需要对所有元素排个序, 然后, 我们把比 a[i] 小两倍的数的下标放进线段树, 那么一会统计的时候, 我们只需要找i + 1 到 n - 1这个范围的和就行了.

[6, 4, 3, 1]

[1, 3, 4, 6]

我们知道只有比i要小的数字在后面才有能有逆序对的可能. 

 所以1 不可能有逆序对产生, 而3可以有逆序对, 因为他前面有一个1比他小, 然后我们把1的下表放进线段树, 就是3, 然后我们找3的下表 + 1, 到n - 1的区间和, 你会算到1, 因为我们只把1放进去, 原理就是这样了.

    vector<long long> v;
    
    //segment tree update
    void update(int l, int r, int y, int x) {
        if (y < l || y > r) return;
        if (l == r) {
            v[x] = 1;
            return;
        }
        
        int mid = l + (r - l) / 2;
        if (y <= mid) update(l, mid, y, 2 * x + 1);
        else update(mid + 1, r, y, 2 * x + 2);
        
        v[x] = v[2 * x + 1] + v[2 * x + 2];
    } 
    
    //segment tree range sum query
    long long query(int l, int r, int ql, int qr, int x) {
        if (qr < l || ql > r) {
            return 0;
        }
        if (l >= ql && r <= qr) {
            return v[x];
        }
        
        int mid = l + (r - l) / 2;
        long long xx  = query(l, mid, ql, qr, 2 * x + 1);
        long long yy = query(mid + 1, r, ql, qr, 2 * x + 2);
        return xx + yy;
    }
    
    int reversePairs(vector<int>& nums) {
        int n = nums.size();
        
        v.resize(4 * n, 0);
        
        vector<pair<long long, long long> > vp(n);
        
        for (int i = 0; i < n; i++) {
            vp[i] = {nums[i], i}; 
        }
        
        sort(vp.begin(), vp.end());

        long long j = 0, ans = 0;
        for (int i = 0; i < n; i++) {
            while (j < n && 2 * vp[j].first < vp[i].first) {
                update(0, n - 1, vp[j].second, 0);
                j++;
            }
            ans += query(0, n - 1, vp[i].second + 1, n - 1, 0);
            
        }
        
        return ans;
    }

P2073

要同时维护最小值, 最大值, 还要支持删除操作. 

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<queue>
#include<map>
using namespace std;
const int oo=1e9 + 7;
const int n = 1000000;
int a[1100000];
int mi[4100000], ma[4100000];
void pushup(int x){
    mi[x] = min(mi[x<<1], mi[x<<1|1]);
    ma[x] = max(ma[x<<1], ma[x<<1|1]);
}
void build(int x, int l, int r){
    if(l == r){
        mi[x] = oo;
        ma[x] = 0;
        return;
    }
    int mid = (l + r)>>1;
    build(x<<1, l, mid);
    build(x<<1|1, mid+1, r);
    pushup(x);
}
void add(int x, int l, int r, int y){
    if(l == r){
        mi[x] = l;
        ma[x] = l;
        return;
    }
    int mid = (l + r)>>1;
    if(y <= mid)add(x<<1, l, mid, y);
    else add(x<<1|1, mid+1, r, y);
    pushup(x);
}
void remove(int x, int l, int r, int y){
    if(l == r){
        mi[x] = oo;
        ma[x] = 0;
        return;
    }
    int mid = (l + r)>>1;
    if(y <= mid) remove(x<<1, l, mid, y);
    else remove(x<<1|1, mid+1, r, y);
    pushup(x);
}
int main(){
    build(1, 1, n);
    int k, l, r;
    long long sum1 =0, sum2=0;
    while(1){
        cin >> k;
        cout << k;
        if(k == 1){
            cin >> l >> r;
            if(a[r] == 0){
                a[r] = l;
                sum1+=l;
                sum2+=r;
                add(1, 1, n, r);
            }
        }else if(k == 3){
            if(mi[1] == oo)continue;
            sum1 -= a[mi[1]];
            sum2 -= mi[1];
            a[mi[1]] = 0; 
            remove(1, 1, n, mi[1]);
        }else if (k == 2){
            if(ma[1] == 0)continue;
            sum1-=a[ma[1]];
            sum2-=ma[1];
            a[ma[1]] = 0;
            remove(1, 1, n, ma[1]);
        }else{
            cout << sum1 << " " << sum2 << endl;
            return 0;
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值