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。