Day2

树状数组

  • 二叉树比较好看,所以,先从它下手
    二叉树
    =>\(C[i] = A[i - 2^k+1] + A[i - 2^k+2] + ... + A[i]\)
  • 那我们可以得到\(SUMi = C[i] + C[i-2^{k_1}] + C[(i - 2^{k_1}) - 2^{k_2}] + .....\)
  • 然后\(2^k\)这么好看的东西怎么能放过呢?于是就有\(2^k\) = i&(-i)
  • 具体怎么得到的。。。我也不知道。(所以从网上抄一段

这里利用的负数的存储特性,负数是以补码存储的,对于整数运算 x&(-x)有
    ● 当x为0时,即 0 & 0,结果为0;
    ●当x为奇数时,最后一个比特位为1,取反加1没有进位,故x和-x除最后一位外前面的位正好相反,按位与结果为0。结果为1。
    ●当x为偶数,且为2的m次方时,x的二进制表示中只有一位是1(从右往左的第m+1位),其右边有m位0,故x取反加1后,从右到左第有m个0,第m+1位及其左边全是1。这样,x& (-x) 得到的就是x。
    ●当x为偶数,却不为2的m次方的形式时,可以写作x= y * (2k)。其中,y的最低位为1。实际上就是把x用一个奇数左移k位来表示。这时,x的二进制表示最右边有k个0,从右往左第k+1位为1。当对x取反时,最右边的k位0变成1,第k+1位变为0;再加1,最右边的k位就又变成了0,第k+1位因为进位的关系变成了1。左边的位因为没有进位,正好和x原来对应的位上的值相反。二者按位与,得到:第k+1位上为1,左边右边都为0。结果为2k。
    总结一下:x&(-x),当x为0时结果为0;x为奇数时,结果为1;x为偶数时,结果为x中2的最大次方的因子

  • 而且这个有一个专门的称呼,叫做lowbit,即取\(2^k\)

【模版】 树状数组 1

题目描述

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

将某一个数加上 x

求出某区间每一个数的和

输入格式

第一行包含两个正整数 n,mn,m,分别表示该数列数字的个数和操作的总个数。

第二行包含 n 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。

接下来 m 行每行包含 3 个整数,表示一个操作,具体如下:

1 x k 含义:将第 xx 个数加上 k

2 x y 含义:输出区间 [x,y] 内每个数的和

输出格式

输出包含若干行整数,即为所有操作 22 的结果。

输入

5 5
1 5 4 2 3
1 1 3
2 2 5
1 3 -1
1 4 2
2 1 4

输出

14
16

说明/提示

【数据范围】
对于 30% 的数据,\(1<=n<=8,1<=m<=10\)
对于 70% 的数据, \(1<=n, m<=10^4\)
对于 100% 的数据,\(1<=n, m<=5*10^5\)

using namespace std;
int n, m, a, b, x, k;
int s[500005];
void fix(int x, int v)
{
	for(int i=x; i<=n; i+= i&(-i)) s[i]+=v;
}
int find(int x)
{
	int ret = 0;
	for(int i=x; i; i-= i&(-i)) ret+=s[i];
	return ret;
}
int main()
{
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; i++)
	{
		scanf("%d", &a);
		fix(i, a);
	}
	for(int i=1; i<=m; i++)
	{
		scanf("%d%d%d", &b, &x, &k);
		if(b == 1) fix(x, k);
		if(b == 2) printf("%d\n", find(k)-find(x-1));
	}
	return 0;
}
  • 常规操作orz

【模版】 树状数组 2

题目描述

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

1.将某区间每一个数数加上x

2.求出某一个数的值

输入格式

第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。

第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。

接下来M行每行包含2或4个整数,表示一个操作,具体如下:

操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k

操作2: 格式:2 x 含义:输出第x个数的值

输出格式

输出包含若干行整数,即为所有操作2的结果。

输入

5 5
1 5 4 2 3
1 2 4 2
2 3
1 1 5 -1
1 3 5 7
2 4

输出

6
10

说明/提示

时空限制:1000ms,128M

数据规模:
对于30%的数据:\(N<=8,M<=10\)
对于70%的数据:\(N<=10000,M<=10000\)
对于100%的数据:\(N<=500000,M<=500000\)

#include<cstdio>
using namespace std;
int n, m, a, b, x, k;
int s[500005], l[500005], p[500005];
void fix(int x, int v)
{
	for(int i=x; i<=n; i+= i&(-i)) s[i]+=v;
}
void zdx(int x, int y, int z)
{
	fix(x, z);
	fix(y+1, -z);
	return ;
}
int find(int x)
{
	int ret = 0;
	for(int i=x; i; i-= i&(-i)) ret+=s[i];
	return ret;
}
int main()
{
	scanf("%d%d", &n, &m);
	for(int i=1; i<=n; i++) 
	{
		scanf("%d", &p[i]);
		l[i] = p[i]-p[i-1];
		fix(i, l[i]);
	}
//	for(int i=1; i<=n; i++) l[i] = p[i]-p[i-1];
//	for(int i=1; i<=n; i++) fix(i, l[i]);
	while(m--)
	{
		int x=0, y=0, z=0;
		scanf("%d", &b);
		if(b == 1)
		{
			scanf("%d%d%d", &x, &y, &z);
			zdx(x, y, z);
		}
		if(b == 2)
		{
			scanf("%d", &x);
			printf("%d\n", find(x));
		}
	}
	return 0;
}
  • 这里是区间更新,单点查询
  • 假设我们规定A[0] = 0,则有\(A[i]=\sum_{j}^i=1\rightarrow D[j];(D[j]=A[j]-A[j-1])\),即前面i项的差值和。

中位数

题目描述

给出一个长度为NN的非负整数序列\(A_i\),对于所有\(1≤k≤(N+1)/2\),输出\(A_1, A_3, …, A_{2k - 1}\)的中位数。即前1,3,5,…1,3,5,…个数的中位数。

输入格式

第1行为一个正整数N,表示了序列长度。
第2行包含N个非负整数\(A_i (A_i ≤ 10^9)\)

输出格式

\((N+1)/2\)行,第i行为\(A_1, A_3, …, A_{2k - 1}\)的中位数。

输入

7
1 3 5 7 9 11 6

输出

1
3
5
6

说明/提示

对于20%的数据,N ≤ 100N≤100;
对于40%的数据,N ≤ 3000N≤3000;
对于100%的数据,N ≤ 100000N≤100000。

#include<cstdio>
#include<queue>
#include<vector>
using namespace std;
priority_queue<int, vector<int>, greater<int> > s;
priority_queue<int> l;
int n, mid;
int a[100005];
int main()
{
    scanf("%d", &n);
    scanf("%d", &a[1]);
    mid = a[1];
    printf("%d\n", mid);
    for(int i=2; i<=n; i++)
	{
        scanf("%d", &a[i]);
        if(a[i]>mid) s.push(a[i]);
        else l.push(a[i]);
        if(i%2==1) 
		{
            while(l.size()!=s.size())
			{
                if(l.size()>s.size())
				{
                    s.push(mid);
                    mid = l.top();
                    l.pop();
                }
                else{
                    l.push(mid);
                    mid = s.top();
                    s.pop();
                }
            }
            printf("%d\n", mid);
        }
    }
    return 0;
}
  • 这个是优先队列,记住priority_queue
  • 然后,小根堆维护较大的值,大根堆维护较小的值;
  • 则,显然,小根堆的堆顶是较大的数中最小的,大根堆的堆顶是较小的数中最大的;
  • 那么将大于大根堆堆顶的数的数放入小根堆\(\leq\)大根堆堆顶的数的数放入大根堆,就保证了所有大根堆中的元素\(<\)小根堆中的元素。
  • 于是,我们不难发现
    • 对于大根堆的堆顶元素,有【小根堆的元素个数】个元素比该元素大,【大根堆的元素个数-1】个元素比该元素小;
    • 同理,对于小跟堆的堆顶元素,有【大根堆的元素个数】个元素比该元素小,【小根堆的元素个数-1】个元素比该元素大。
  • 姑且记【小根堆的元素个数】为\(A_i\),【大根堆的元素个数】为\(B_i\)
  • 那么\((B_i - A_i) \leq 1\),元素个数较多的堆的堆顶元素即为当前中位数。
  • 于是,就有维护方式:把元素个数多的堆的堆顶元素取出,放入元素个数少的堆。
  • 结尾,感谢liuziwen同学(大佬)提供的本题思路,orz。
posted @ 2020-01-16 21:56  orange_lyc  阅读(91)  评论(0编辑  收藏  举报