zoukankan      html  css  js  c++  java
  • 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
    ", 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
    ", find(x));
    		}
    	}
    	return 0;
    }
    
    • 这里是区间更新,单点查询
    • 假设我们规定A[0] = 0,则有(A[i]=sum_{j}^i=1 ightarrow 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
    ", 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
    ", mid);
            }
        }
        return 0;
    }
    
    • 这个是优先队列,记住priority_queue
    • 然后,小根堆维护较大的值,大根堆维护较小的值;
    • 则,显然,小根堆的堆顶是较大的数中最小的,大根堆的堆顶是较小的数中最大的;
    • 那么将大于大根堆堆顶的数的数放入小根堆(leq)大根堆堆顶的数的数放入大根堆,就保证了所有大根堆中的元素(<)小根堆中的元素。
    • 于是,我们不难发现
      • 对于大根堆的堆顶元素,有【小根堆的元素个数】个元素比该元素大,【大根堆的元素个数-1】个元素比该元素小;
      • 同理,对于小跟堆的堆顶元素,有【大根堆的元素个数】个元素比该元素小,【小根堆的元素个数-1】个元素比该元素大。
    • 姑且记【小根堆的元素个数】为(A_i),【大根堆的元素个数】为(B_i)
    • 那么((B_i - A_i) leq 1),元素个数较多的堆的堆顶元素即为当前中位数。
    • 于是,就有维护方式:把元素个数多的堆的堆顶元素取出,放入元素个数少的堆。
    • 结尾,感谢liuziwen同学(大佬)提供的本题思路,orz。
  • 相关阅读:
    液晶显示器分辨设置,显示器分辨率设置……
    如何显示语言栏
    查看一键Ghost的备份文件
    百度空间的变迁
    CentOS U盘安装
    Linux服务器系统选择
    博客一夜回到解放前
    spark常见的transformation和action算子
    二分查找
    9:两个栈实现一个队列
  • 原文地址:https://www.cnblogs.com/orange-233/p/12203457.html
Copyright © 2011-2022 走看看