Jeanny
寂兮,寥兮,独立不改,周行而不殆

树状数组本质是什么呢?其实是二进制数。例如6如果用二进制数来表示6=0110=4+2 , 13=1101=8+4+1 . 因此我们可以用一个c[]数组来表示管辖范围,c[13]=c[13]+c[12]+c[8]. 那么求1~13的和就不用a[1]+a[2]+…a[13]了,而是可以直接c[13]+c[12]+c[8]。

15 = 8 + 4 + 2 + 1
(14,15] (15-1, 15] (1110, 1111]
(12,14] (14-2, 14] (1100, 1110]
(8,12] (12-4, 12] (1000, 1100]
(0,8] (8-8, 8] (0000, 1000]
减掉的是每一个二进制的尾巴,1,10,100,1000,也就是管辖范围
这个可以用lowbit函数去求。

我们可以看出刚才的13是1101有8,4,1构成,倒序来写就是13=1+4+8。我们可以先把1减掉,12=4+8,再把4减掉,8=8,再把8减掉8-8=0.这样一步一步求和运算。对应的1,4,8就是末尾1对应的位置,也就是大牛博客里讲到的Lowbit=x & (-x).这里不赘言。

找子节点(程序中不涉及找子节点的过程):

12(1100)是哪些节点的父节点
1.12(1100)管辖范围是4,所以子节点在8到12之间2.将12-1得到11(1011,第一个子节点)
3.11(1011)的管辖范围是1减掉得到10(1010)
4.10(1010)的管辖范围是2减掉得到8(1000)
5.所以12的直接子节点是: 11,10

找父节点:(那么如何能把a[]数组转换为c[]数组呢)?

1.父节点一定比自己x大需要加多少的数字即为父节点
2.父节点-管辖范围<=x-lowbit(x)
这样看来我们可以找到规律如果在x末尾1后面加1,必然管辖范围就是刚刚加的1对应的十进制减掉管辖范围肯定不满足条件2
例如:x=1010(10)
最后一位加1变成1011假设是x的父节点,因此它的父节点管辖范围就是1,1011-0001=1010(10),还是它自己
显然不满足 1010 <= 1010-lowbit(1010)=1000 (它自己减一个正数) 再如:x=10100 同样往末位1的后面加1则减掉管辖范围是x。也不满足 10100 <= 10100 - lowbit(10100)。
相同的道理,如果在倒数第二位加1,父节点为10110, 减掉lowbit(10110),依然不满足 <= 10100-lowbit(10100).
那么,哪一个才是比x本身大的第一个数字,且x末尾1的后面几位都不是1?
例如,x=10100
10100 + 0011 + 1 = 11000
即 x += lowbit(x)

lowbit//-x=取反+1

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

构建父节点c[]

void update(int p,int x){  
    while(p<=n){  
        c[p]+=x;  
        p+=lowbit(p);  
    }  
} 

区间求和

int sum(int p){  
    int sum=0;  
    while(p>0){  
        sum+=c[p];  
        p-=lowbit(p);  
    }  
    return sum;  
} 

 

P3374 【模板】树状数组 1

如题,已知一个数列,你需要进行下面两种操作:

  • 将某一个数加上 x

  • 求出某区间每一个数的和

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int n,m,c[500005];
int lowbit(int x){
  return x & (-x);
}
void add(int i,int x){
  while(i <= n){
    c[i] += x;
    i += lowbit(i);
  }
}
int sm(int i){
  int sum = 0;
  while(i){
    sum += c[i];
    i -= lowbit(i);
  }
  return sum;
}
int main(){
  scanf("%d%d",&n,&m);
  int v;
  for(int i = 1; i <= n; i++){
    scanf("%d",&v);
    add(i,v);
  }
  int p,x,y,sl,sr;
  for(int i = 1; i <= m; i++){
    scanf("%d%d%d",&p,&x,&y);
    if(p == 1){
      add(x,y);
    }
    if(p == 2){
      sr = sm(y);
      sl = sm(x - 1);
      printf("%d\n",sr - sl);
    }
  }
  return 0;
}

&最长上升子序列 LIS(权值数状数组)

数值作为下标,比如当前是5,需要从1~4取找最大值,+1就是f[i]。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int a[1000005], b[1000005], f[1000005], n, k, c[1000005], ans;
int lowbit(int x) { return x & (-x); }
void update(int p, int x) {
    while (p <= k) {
        c[p] = max(c[p], x);
        p += lowbit(p);
    }
}
int query(int x) {
    int mx = 0;
    while (x) {
        mx = max(mx, c[x]);
        x -= lowbit(x);
    }
    return mx;
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        b[i] = a[i];
    }
    sort(b + 1, b + n + 1);
    k = unique(b + 1, b + n + 1) - (b + 1);
    for (int i = 1; i <= n; i++) {
        int x = lower_bound(b + 1, b + k + 1, a[i]) - b;
        f[i] = query(x - 1) + 1; 
        update(x, f[i]);
        ans = max(ans, f[i]);
    }
    cout << ans << endl;
    return 0;
}

 

 

&AcWing 3662 最大上升子序列的和 https://www.acwing.com/problem/content/3665/

&逆序对

 

241. 楼兰图腾 https://www.acwing.com/problem/content/243/

 分别统计i位置左边比a[i]小的数的个数m、右边比a[i]小的数的个数n,运用乘法原理:
1. 第一步从左边m个数中任选一个,有m种选法
2. 第二步从右边n个数中任选一个,有n种选法

权值树状数组,统计比自己大的和。

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
int c[200005], a[200005], n, h[200005], l[200005];
ll res, ans;
int lowbit(int x){
	return x & (-x);
}
void add(int i, int x){
	for(; i <= n; i += lowbit(i))
		c[i] += x;	 
}
int sum(int i){
	int sm = 0;
	for(; i; i -= lowbit(i))
		sm += c[i];
	return sm;
}
int main(){
	scanf("%d",&n);
	for(int i = 1; i <= n; i++)
		scanf("%d",&a[i]);
	for(int i = 1; i <= n; i++){
		h[i] = sum(n) - sum(a[i]);
		l[i] = sum(a[i]-1);
		add(a[i], 1);
	}
	memset(c, 0, sizeof c);
	for(int i = n; i >= 1; i--){
		ans += 1ll * h[i] * (sum(n) - sum(a[i]));
		res += 1ll * l[i] * sum(a[i]-1);
		add(a[i], 1);
	}
	printf("%lld %lld",ans, res);
	return 0;
} 

acwing

 

输入样例:

10 5
1 2 3 4 5 6 7 8 9 10
Q 4
Q 1
Q 2
C 1 6 3
Q 2

输出样例:

4
1
2
5
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n, m, a[100005], p;
ll c[100005];
char s[3];
int lowbit(int i){
	return i & (-i);
}
void add(int i, int x){
	for(; i <= n; i += lowbit(i)){
		c[i] += x;
	} 
}
ll sum(int i){
	ll sm = 0;
	for(; i; i -= lowbit(i)){
		sm += c[i];
	}
	return sm;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1; i <= n; i++){
		scanf("%d",&a[i]);
	}	
	for(int i = 1; i <= n; i++){
		add(i, a[i] - a[i-1]);
	}
	for(int i = 1; i <= m; i++){
		int l,r,x;
		scanf("%s",s);
		if(s[0] == 'Q'){
			scanf("%d",&p);
			printf("%lld\n",sum(p));
		}
		else{
			scanf("%d%d%d",&l,&r,&x);
			add(l, x);
			add(r+1, -x);
		}
	} 
	return 0;
} 

MIS 

题目描述

长度为m的递增子序列(M length Increase Subsequence),称为MIS。

求长度为n的序列,有多少个MIS。

子序列不需要在原序列中不需要连续,但要保证相对位置。

输入格式

第一行两个整数n和m

第二行n个整数,表示一个序列。

输出格式

输出MIS的个数,结果对20140921取余。

样例

输入

5 3

1 2 5 3 4

输出

5

数据范围与提示

样例解释:[1 2 5],[1 2 3],[1 2 4],[1 3 4],[2 3 4] 都是MIS。

对于30%的数据,n的范围[1,30],m的范围[1,10];

对于60%的数据,n的范围[1,500],m的的范围[1,50];

对于80%的数据,n的范围[1,1000],m的的范围[1,100],序列中的元素范围[1,n];

对于100%的数据,n的范围[1,10000],m的的范围[1,100],序列中的元素范围[1,109];

比较容易想到60分的做法:</br>
g[i][k]表示第i位在此上升子序列中排k名的方案数</br>
则: g[i][k] += g[j][k-1], 其中a[i] > a[j], g[j][k-1]表示第j位的数字在此上升子序列中排k-1名 </br>
```
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n, m, a[5005], g[5005][5005], ans, res;
signed main(){
    scanf("%lld%lld",&n,&m);
    for(int i = 1; i <= n; i++) scanf("%lld",&a[i]);
    for(int i = 1; i <= n; i++) g[i][1] = 1;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j < i; j++){
            for(int k = 2; k <= m; k++){
                if(a[i] > a[j]){
                    g[i][k] += g[j][k-1];
                }
            }
        }
        res = (res + g[i][m]) % 20140921;
    }
    printf("%lld\n",res % 20140921);
    return 0;
}
```
优化:考虑如果g[i][k]累加的过程如果可以log级别查询到(求和),则总时间复杂度降为nlogn</br>
思考:当枚举到第p位时,在p-1位以前,比a[p]小且在此子序列中排名j的方案数之和,用树状数组维护"":</br>
修改:数值i在子序列中排名为j的树状数组f[i][j]依次向上累加其方案数。</br>
```
void add(int i, int j, int x){ 
    for(; i <= n; i += lowbit(i)){
        g[i][j] = (g[i][j] + x) % 20140921;
    }
}
```
查询:查询权值小于等于i,且排名为j的方案数 
```
int query(int i, int j){
    int sum = 0;
    for(; i; i -= lowbit(i)){
        sum = (sum + g[i][j]) % 20140921;
    }
    return sum % 20140921; 
} 
```
权值树状数组,需要用到离散化。</br>
完整代码如下:</br>
```
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int g[10005][105], n, m, a[10005], b[10005];
int lowbit(int x){
    return x & (-x);
}
void add(int i, int j, int x){
    for(; i <= n; i += lowbit(i)){
        g[i][j] = (g[i][j] + x) % 20140921;
    }
}
int query(int i, int j){
    int sum = 0;
    for(; i; i -= lowbit(i)){
        sum = (sum + g[i][j]) % 20140921;
    }
    return sum % 20140921; 
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= n; i++){
        scanf("%d",&a[i]);
        b[i] = a[i];
    }
    sort(b+1, b+n+1);
    int k = unique(b+1, b+n+1) - (b+1); 
    for(int i = 1; i <= n; i++){
        int x = lower_bound(b+1, b+k+1, a[i]) - b;
        add(x,1,1);//权值下标,排位,+1。每个数字自己作为上升序列的第一个位,其中排名就为1
        for(int j = 2; j <= m; j++){
            int sum = query(x-1, j-1);//查询权值比x小的,且排名为j-1的方案数
            add(x, j, sum);//权值x排名为j的下标,排j-1位,+sum 
        } 
    }
    printf("%d\n",query(k,m) % 20140921); 
    return 0;
} 
 
``` 

 

1376 最长递增子序列的数量

 http://www.51nod.com/Challenge/Problem.html#problemId=1376

数组A包含N个整数(可能包含相同的值)。设S为A的子序列且S中的元素是递增的,则S为A的递增子序列。如果S的长度是所有递增子序列中最长的,则称S为A的最长递增子序列(LIS)。A的LIS可能有很多个。例如A为:{1 3 2 0 4},1 3 4,1 2 4均为A的LIS。给出数组A,求A的LIS有多少个。由于数量很大,输出Mod 1000000007的结果即可。相同的数字在不同的位置,算作不同的,例如 {1 1 2} 答案为2。

输入

第1行:1个数N,表示数组的长度。(1 <= N <= 50000)
第2 ~ N+1行:每行1个数A[i],表示数组的元素(0 <= A[i] <= 10^9)

输出

输出最长递增子序列的数量Mod 1000000007。

输入样例

5
1
3
2
0
4

输出样例

2
https://dandelioncloud.cn/article/details/1453017587545395202
//单点修改, 区间维护两个值,LIS以及最大
// #include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#define N 500005
using namespace std;
const int P = 1e9+7;
struct Node{
    int len, cnt;
}c[N], dp[N];
int a[N], b[N], n, k;
int lowbit(int x){
    return x & (-x);
}
void update(Node &tmp, Node vl){
    if(tmp.len == vl.len){
        tmp.cnt += vl.cnt, tmp.cnt %= P;
    }else if(tmp.len < vl.len){
        tmp = vl;
    }
}
void add(int i, Node vl){
    for(; i <= k; i += lowbit(i)){
        update(c[i], vl);
    }
}
Node query(int i){
    Node mx = (Node){0, 1};//一开始写的(Node){0,0}
    for(; i; i -= lowbit(i)){
        update(mx, c[i]);
    }
    return mx;
}
int main(){
    scanf("%d",&n);
    for(int i = 1; i <= n; i++){
        scanf("%d",&a[i]);
        b[i] = a[i];
    } 
    sort(b+1, b+n+1);
    k = unique(b+1, b+n+1) - (b+1);
    for(int i = 1; i <= n; i++){
        a[i] = lower_bound(b+1, b+k+1, a[i]) - b;
        dp[i] = query(a[i]-1);
        dp[i].len++;
        add(a[i], dp[i]); //单点修改: 以它结尾的LIS长度, 相同长度的序列个数1
    }
    Node ans = query(k);//query(n)一开始写的n
    printf("%d\n", ans.cnt);
    // Node ans = (Node){0,0};
    // for(int i = 1; i <= n; i++){
    //     update(ans, dp[i]);
    // }
    // printf("%d\n",ans.cnt);
    return 0;
}

abc_368G  [ABC368G] Add and Multiply Queries

G:
# [ABC368G] Add and Multiply Queries


给定两个长度为 $ N $ 的正整数序列 $ A, B $。需要处理 $ Q $ 个按顺序给出的查询。查询有以下三种类型:

- 类型 $ 1 $:格式为 `1 i x`。将 $ A_i $ 替换为 $ x $。
- 类型 $ 2 $:格式为 `2 i x`。将 $ B_i $ 替换为 $ x $。
- 类型 $ 3 $:格式为 `3 l r`。需要解决以下问题并输出答案:
- 初始时 $ v = 0 $。依次对 $ i = l, l + 1, \dots, r $ 进行操作,每次操作将 $ v $ 替换为 $ v + A_i $ 或 $ v \times B_i $。求最终能得到的 $ v $ 的最大值。
需要注意的是,输入中类型 $ 3 $ 的查询的答案保证在 $ 10^{18} $ 以下。


### 制約

- $ 1 \leq N \leq 10^5 $
- $ 1 \leq A_i \leq 10^9 $
- $ 1 \leq B_i \leq 10^9 $
- $ 1 \leq Q \leq 10^5 $
- 类型 $ 1 $, $ 2 $ 的查询中,$ 1 \leq i \leq N $
- 类型 $ 1 $, $ 2 $ 的查询中,$ 1 \leq x \leq 10^9 $
- 类型 $ 3 $ 的查询中,$ 1 \leq l \leq r \leq N $
- 类型 $ 3 $ 的查询中,输出值在 $ 10^{18} $ 以下

类型 3 的查询的答案保证在 $10^18$,约等于$2^62$,也就是$10^5$个数字中只有60多次大于2的情况,其余情况都是1.
这样的话,我们大多数连续的区间,都是要选择A序列进行相加。
可以吧B序列中非1的位置放入set中,每次找到1的区间,树状数组求和,快速加给v。B序列单独比较非1的情况。

一开始想不明白:
1.当前询问在上一个询问基础上加减(傻X)
2.错误:没有修改:b[loc] = x;
3.错误:v = max(v+a[pos], v*b[pos]); //写成 v+= ....

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n, a[100005],b [100005], c[100005], q, op, res;
set<int> s;
int lowbit(int x){
    return x & (-x);
}
void update(int i, int x){
    while(i <= 100005){//
        c[i] += x, i += lowbit(i);
    }
}
int sum(int i){//前缀和
    int ans = 0;
    while(i){
        ans += c[i]; i -= lowbit(i);
    }
    return ans;
}
int loc, x;
signed main(){
    cin>>n;
    for(int i = 1; i <= n; i++){
        cin>>a[i]; update(i, a[i]);
    }
    for(int i = 1; i <= n; i++){
        cin>>b[i];
        if(b[i] > 1) s.insert(i);
    }
    //
    s.insert(n);
    cin>>q;
    for(int i = 1; i <= q; i++){
        cin>>op>>loc>>x;
        if(op == 1){
            update(loc, x - a[loc]);
            a[loc] = x;
        }
        else if(op == 2){
            if(b[loc] == 1){
                if(x != 1) s.insert(loc);
            }
            else{
                if(x == 1) s.erase(loc);
            }
            b[loc] = x;
        }
        else{       
            int v = a[loc++], r = x;//第一个数字一定是加到v中
            set<int>::iterator it = s.lower_bound(loc);
            while(loc <= r){
                int pos = *it;
                v += sum(min(pos-1, r)) - sum(loc-1);
                if(pos > r) break;
                v = max(v+a[pos], v*b[pos]); //写成 v+= ....
                loc = pos+1;
                it++;
            }
            cout<<v<<endl;
        }
    }
    return 0;
}

 

 

洛谷 P5156 [USACO18DEC]Sort It Out P

省选模拟题 https://www.luogu.com.cn/problem/solution/P5156

 
posted on 2022-08-29 21:42  Jeanny  阅读(91)  评论(0)    收藏  举报