2019.7.15-7.20暑假集训总结

匿名 (未验证) 提交于 2019-12-02 23:49:02

//7.18前三天的等我周日再继续复习总结orz_(:з」∠)_每一天都是充实的欧

7.15:差分与前缀和

7.16: 倍增与ST表

7.17:并查集进阶

7.18:树状数组与线段树

树状数组:

1.引入lowbit(x)

int lowbit(int i)
{

}

-11=(0100+1)=0101

&1011

树状数组 c[x],比如c[4],4的二进制是(100),100刚好是3位数,所以这个树状数组到c[4]为止高度是3。

除了树根,c[x]的父节点为 c[x+lowbit(x)]

能用树状数组尽量用树状数组,快,省空间。(一般用来单点更新,区间求和,维护最大值二不能维护最小值。但是求区间最大值的时候复杂度比较高容易TLE,这个时候还是需要用到线段树

“细细观察二进制 树状数组追其根本就是二进制的应用”

 1 #include<bits/stdc++.h>  2 using namespace std;  3 const int M =100000+100;  4 int n,t;  5 int a[M],C[M];  6 int lowbit(int x)  7 {  8     return x&(-x);  9 } 10 int sum(int x) 11 { 12     int ret=0; 13     while(x) 14     { 15         ret+=C[x]; 16         x-=lowbit(x); 17     } 18     return ret; 19 } 20 void updata(int x,int d) 21 { 22     while(x<=n) 23     { 24         C[x]+=d; 25         x+=lowbit(x); 26     } 27 } 28 int main() 29 { 30     scanf("%d",&t); 31  32     for(int i=1;i<=t;i++) 33     { 34         printf("Case %d:\n",i); 35         scanf("%d",&n); 36         for(int i=1;i<=n;i++) 37             C[i]=0; 38         for(int i=1;i<=n;i++) 39         { 40             scanf("%d",&a[i]); 41             updata(i,a[i]); 42         } 43         char s[10]; 44         while(scanf("%s",s)&&s[0]!='E') 45         { 46             int q,w; 47             scanf("%d%d",&q,&w); 48             if(s[0]=='A') 49                 updata(q,w); 50             else if(s[0]=='S') 51                 updata(q,-w); 52             else 53                 printf("%d\n",sum(w)-sum(q-1)); 54         } 55     } 56     return 0; 57 }
树状数组单点更新
 1 #include<iostream>  2 #include<cstdio>  3 //#include<bits/stdc++.h>  4 using namespace std;  5 typedef long long ll;  6 const int M = 100000+100;  7 ll a[M];  8 ll sum[M];  9 ll c[2][M]; 10 int n; 11 int lowbit(int x) 12 { 13     return x&(-x); 14 } 15 void add(int x,int d,int k) 16 { 17     while(x<=n) 18     { 19         c[k][x]+=d; 20         x+=lowbit(x); 21     } 22 } 23 ll ask(int x,int k) 24 { 25     ll ans=0; 26     while(x) 27     { 28         ans+=c[k][x]; 29         x-=lowbit(x); 30     } 31     return ans; 32 } 33 int main() 34 { 35     int q; 36     cin>>n>>q; 37     for(int i =1 ;i <= n;i++) 38     { 39         scanf("%I64d",&a[i]); 40         sum[i]=sum[i-1]+a[i]; 41     //    cout<<sum[i]<<endl; 42     } 43     char s[2]; 44     int l,r; 45     ll ans=0; 46     for(int i = 1; i <=q; i++) 47     { 48         scanf("%s%d%d",s,&l,&r); 49         if(s[0]=='C') 50         { 51             int d; 52             scanf("%d",&d); 53             add(r+1,-d,0),add(r+1,-(r+1)*d,1); 54             add(l,d,0),add(l,l*d,1); 55         } 56         else 57         { 58             ans=sum[r]+(r+1)*ask(r,0)-ask(r,1); 59             ans-=sum[l-1]+l*ask(l-1,0)-ask(l-1,1); 60             printf("%I64d\n",ans); 61         } 62     } 63     return 0; 64  } 
树状数组区间更新与区间查询

线段树:

满足区加法。

当build建树的时候,是先向下递归,再从下到上逐步更新。

 1 /*  2 区间更新   求区间和   3 */  4 //#include<bits/stdc++.h>  5 #include<cstdio>  6 #include<iostream>  7 #include<cstring>  8 #include<algorithm>  9 typedef long long ll; 10 using namespace std; 11 #define maxn 100000+100 12 ll sum[maxn<<2]; 13 ll lazy[maxn<<2]; 14 //int a[maxn<<2]; 15 void build(int l,int r,int rt) 16 { 17     lazy[rt]=0; 18     if(l==r) 19     { 20         scanf("%lld",&sum[rt]); 21         return; 22     } 23     int mid=(l+r)/2; 24     build(l,mid,rt<<1); 25     build(mid+1,r,rt<<1|1); 26     sum[rt]=sum[rt<<1]+sum[rt<<1|1]; 27 } 28 void pushdown(int l,int r,int rt)  29 { 30     if(lazy[rt]) 31     { 32         lazy[rt<<1]+=lazy[rt]; 33         lazy[rt<<1|1]+=lazy[rt]; 34         int mid=(l+r)/2; 35         sum[rt<<1]+=(mid-l+1)*lazy[rt]; 36         sum[rt<<1|1]+=(r-mid)*lazy[rt]; 37         lazy[rt]=0; 38     } 39 } 40 void change(int l,int r,int rt,int L,int R,int c) 41 { 42     if(L<=l&&r<=R) 43     { 44         lazy[rt]+=(ll)c; 45         sum[rt]+=(r-l+1)*(ll)c; 46         return; 47     } 48     pushdown(l,r,rt); 49     int mid=(l+r)/2; 50     if(R>mid) 51     change(mid+1,r,rt<<1|1,L,R,c); 52     if(L<=mid) 53     change(l,mid,rt<<1,L,R,c); 54     sum[rt]=sum[rt<<1]+sum[rt<<1|1]; 55 } 56 ll query(int l,int r,int rt,int a,int b) 57 { 58     if(l>=a&&r<=b) 59     return sum[rt]; 60     pushdown(l,r,rt); 61     int mid=(l+r)/2; 62     ll ans=0; 63     if(a<=mid) 64         ans+=query(l,mid,rt<<1,a,b); 65     if(b>mid) 66         ans+=query(mid+1,r,rt<<1|1,a,b); 67     return ans; 68 } 69 int main() 70 { 71     int t,n,K; 72     while(~scanf("%d%d",&n,&t)) 73     { 74         build(1,n,1); 75         char s[3]; 76         while(t--) 77         { 78             int a,b,c; 79             scanf("%s",s); 80             if(s[0]=='C') 81             { 82                 scanf("%d%d%d",&a,&b,&c); 83                 change(1,n,1,a,b,c); 84             } 85             else 86             { 87                 scanf("%d%d",&a,&b); 88                 printf("%lld\n",query(1,n,1,a,b)); 89             } 90         } 91     }     92     return 0; 93 }
线段树区间更新

lazy标记的时候,记得访问后就顺便更新一波,再把最初的懒标记清空为0。

线段树的其他变形应用:逆序对,区间覆盖(把lazy+=d 改成lazy=d),区间最长序列和(区间合并),扫描线,求第k大的某物。

。。

7.19:线段树进阶


遇到了一道题,是poj的Ultra-QuickSort 归并排序。即给出一串数字,求出使用归并排序时数字交换的次数。

归并排序的数字交换次数刚好就是这串数字的逆序数。

例如:5 3 4 2 1

n=5,一共5个数字。

第三步:a[4]=1;因为4<n,所以计算一下从a[n]到a[4+1],那就是a[5],所以ans=1。

第四步:a[2]=1;同理 ans=a[5]+a[4]+a[3]=3;

第五步:a[1]=1;同理ans =a[5]+a[4]+a[3]+a[2]=4。

这里的ans可以开个树状数组记录一下。

代码应用:

2.线段树

离散化概念:一开始感觉离散化?nb啊,看不懂什么意思。。。又想起离散数学,心中感慨万千。

离散化是程序设计中一个非常常用的技巧,它可以有效的降低时间复杂度。其基本思想就是在众多可能的情况中“只考虑我需要用的值”。

这一步有点类似于数据分析,就是第一步清洗数据,否则可能会占用太多内存,garbage in ,garbage out。

所以可以先用vector暂时存一下数据,然后用unique去重,在减去首地址就是它的去重后的大小了。但是注意,vector 的目的并非存储本身,而是离散化。

然后,就像把数据按一定大小顺序重新排序这一步也是离散化。比如贪心。。。

吉司机线段树/势能线段树:

就是这样的题目,不能简单粗暴地用lazy标记,所以要改成 bool flag[i],以确定是否要进行开根号处理,因为这个数比如 153437757 它再大多开几次而且还是向下取整,它也很快就变成1了,所以暴力向下更新即可。据称不超过5次就变成1.

权值线段树:

用来找一棵树的前驱,后继,第k大的x,x是第k大。当然,这棵树还可以删除,插入数字。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!