• 博客园Logo
  • 会员
  • 周边
  • 众包
  • 新闻
  • 博问
  • 闪存
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 简洁模式 ... 退出登录
    注册 登录
Jeanny
寂兮,寥兮,独立不改,周行而不殆
      新随笔       管理     
树状数组

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

我们可以看出刚才的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
最后一位加1变成1011,管辖范围是1,1011-0001=1010,显然不满足<=1010-lowbit(1010)=1000
再如:x=10100
同样,往末位1的后面加1,则减掉管辖范围最小是x,不满足条件2
那么,哪一个才是比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;
}

 

241. 楼兰图腾

 分别统计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;
}

 洛谷 P5156 [USACO18DEC]Sort It Out P

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

posted on 2022-08-29 21:42  Jeanny  阅读(52)  评论(0)  编辑  收藏  举报
会员力量,点亮园子希望
刷新页面返回顶部
Copyright © 2024 Jeanny
Powered by .NET 8.0 on Kubernetes