模板汇总1.1_数据结构1

1.并查集(Set Union)

 1 #include<cstdio>
 2 #include<algorithm>
 3 using namespace std;
 4 const int N=10005;
 5 int n,m,t1,t2,t3;
 6 int aset[N],siz[N];
 7 int find(int x)
 8 {
 9     return (aset[x]==x)?x:aset[x]=find(aset[x]);
10 }
11 int main ()
12 {
13     scanf("%d%d",&n,&m);
14     for(int i=1;i<=n;i++)
15         aset[i]=i,siz[i]=1;
16     for(int i=1;i<=m;i++)
17     {
18         scanf("%d%d%d",&t1,&t2,&t3);
19         if(t1==1)
20         {
21             int f1=find(t2),f2=find(t3);
22             if(siz[f1]<siz[f2]) swap(f1,f2);
23             siz[f1]+=siz[f2],aset[f2]=f1;
24         }
25         else if(t1==2)
26             printf("%c\n",find(t2)==find(t3)?'Y':'N');
27     }
28     return 0;
29 }
View Code 

·按秩合并单次操作$O(\log n)$,路径压缩单次操作$O(\log_{1+m/n} n)$,路径压缩+按秩合并单次操作$O(α(m,n))$

Tarjan的论文

2.树状树组(Binary Indexed Tree)

·前缀和思想

·lowbit

顾名思义(low-bit),$lowbit$是一个数的二进制表示中最末的“$1$”(及其之后的“$0$”)所表示的数。

如何求一个数的$lowbit$?利用二进制补码的原理可知

int lowbit(int x)
{
     return x&-x;      
}

Why lowbit?

树状数组是这样存储原数组元素的↓

C1=A1
C2=A1+A2
C3=A3
C4=A1+A2+A3+A4
C5=A5
C6=A5+A6
C7=A7
C8=A1+A2+A3+A4+A5+A6+A7+A8

我们将总区间以$lowbit$拆分为小区间,以$C[x]$保存原数列里区间$[x-lowbit(x)+1,x]$中所有数的和

·时间复杂度:单次查询&修改皆为 $O(log$ $n)$

·具体实现

 1 #include<cstdio>
 2 const int N=100005;
 3 int bit[N],n,m,q,t1,t2;
 4 void change(int pos,int num)
 5 {
 6     while(pos<=n)
 7         bit[pos]+=num,pos+=pos&-pos;
 8 }
 9 int query(int pos)
10 {
11     int ret=0;
12     while(pos)
13         ret+=bit[pos],pos-=pos&-pos;
14     return ret;
15 }
16 int main ()
17 {
18     scanf("%d%d",&n,&m);
19     for(int i=1;i<=n;i++)
20         scanf("%d",&rd),change(i,rd);
21     for(int i=1;i<=m;i++)
22     {
23         scanf("%d%d%d",&q,&t1,&t2);
24         if(q==1) change(t1,t2);
25         else printf("%d\n",query(t2)-query(t1-1));
26     }
27 }
View Code

·性质

引自李煜东的蓝书

将树状数组视作一个树形结构,它满足如下性质:

1.每个内部节点$C[n]$保存以它为根的子树的所有叶节点的和

2.每个内部节点$C[n]$的子节点数目等于$lowbit(n)$的位数

3.除去树的根(上图中的$C[8]$),每个内部节点$C[n]$的父节点为$C[n+lowbit(n)]$

4.树的深度为$log$ $n$

3.线段树

·一棵二叉树,每个节点表示一段区间

·线段树的每个节点nde代表序列的一段区间[l,r],左子节点nde*2代表[l,mid],右子节点nde*2+1代表[mid+1,r]

·如何进行区间修改/查询?打标记+先拆后合并

如果当前节点代表的区间完全含于当前区间节点代表的区间,直接修改/查询节点并回溯,如果是修改就在这里打上标记

如果当前节点代表的区间完全不含于代表的区间,直接回溯

如果当前节点代表的区间不完全含于节点代表的区间,下放标记。拆开当前节点代表的区间,递归进入当前节点的两棵子树,最后合并答案。

·什么是标记,为什么要使用标记?

修改和查询的naive做法是,递归到叶节点后一级级修改,每次修改的时间复杂度为$O(nlog n)$,瞬间爆炸

打标记:每个节点带一个标记,记录这个区间还未执行的修改,在适当情况下才下放标记

在“完全包含”的情况下,修改数据时同时修改标记,在“不完全包含”的情况下对标记进行下放,时间复杂度$O(log n)$

总结和补充一下:线段树除了递归思想,有两个主要操作:更新(经常被称为$pushup$)和标记下放(经常被称为$pushdown$)。在建树和拆开区间进行修改时,我们通过更新操作维护节点信息,更新操作就是合并两个子节点的信息,一般和和有关的信息就是加起来,和最值有关的信息就是取一下最值,还有一些信息需要考虑左右两个儿子的信息。标记下放就是将根节点标记记录的信息“告知”子节点,具体操作和你“完全包含”时的操作是一样的,下放完后要清空根节点的标记

在建树中或者“拆区间”的更新结束后,你需要进行更新操作,在“拆区间”之前,你需要进行标记下放

一个需要注意的东西:如果线段树维护的几个信息在更新时互相(或者单向)依赖,要先更新依赖最大的。emmm比如说你维护区间和和区间平方和,更新时你要先更新区间平方和(因为$(a+b)^2=a^2+2ab+b^2$,假如你加上$b$要依赖原来的区间和更新$2ab$)

③线段树的时间复杂度与所需空间?

时间复杂度:查询&修改皆为 $O(log$ $n)$

所需空间:$4n$

④线段树的具体实现

  1 #include<cstdio>
  2 #include<cstring>
  3 #include<algorithm>
  4 using namespace std;
  5 const int N=100005;
  6 long long num[N],maxx[4*N],val[4*N],laz1[4*N],laz2[4*N];
  7 long long n,m,ask,x,y,k;
  8 void create(int nde,int l,int r)//nde->当前节点,l->区间左端点,r->区间右端点
  9 {
 10     laz2[nde]=1;//初始化加法懒惰标记为0,乘法懒惰标记为1
 11     if(l==r)//到达叶子级别,直接赋对应值
 12         val[nde]=maxx[nde]=num[l];
 13     else//向下建树
 14     {
 15         int mid=(l+r)/2,ls=2*nde,rs=2*nde+1;//区间中点,左右儿子 
 16         create(ls,l,mid),create(rs,mid+1,r);//建左右儿子
 17         val[nde]=val[ls]+val[rs],maxx[nde]=max(maxx[ls],maxx[rs]);
 18     }
 19 }
 20 void release(int nde,int l,int r)//标记下放
 21 {
 22     int mid=(l+r)/2,ls=2*nde,rs=2*nde+1;
 23     if(laz2[nde]!=1)//如上所述,下放乘法标记
 24     {
 25         laz1[ls]*=laz1[nde],laz1[rs]*=laz1[nde];
 26         laz2[ls]*=laz2[nde],laz2[rs]*=laz2[nde];  
 27         val[ls]*=laz2[nde],val[rs]*=laz2[nde];  
 28         maxx[ls]*=laz2[nde],maxx[rs]*=laz2[nde];laz2[nde]=1;//下放结束,重置爸爸的乘法标记
 29     } 
 30     if(laz1[nde])//如上所述,下放加法标记
 31     {
 32         laz1[ls]+=laz1[nde],laz1[rs]+=laz1[nde];//儿子的标记直接加上爸爸的标记        
 33         val[ls]+=laz1[nde]*(mid-l+1),val[rs]+=laz1[nde]*(r-mid);//儿子的值和最值加上爸爸的标记与区间的乘积
 34         maxx[ls]+=laz1[nde],maxx[rs]+=laz1[nde];laz1[nde]=0;//下放结束,重置爸爸的加法标记
 35     }
 36 }
 37 long long vquery(int nde,int l,int r,int nl,int nr)//查询区间和操作,nl、nr分别为查询区间的左右端点
 38 {
 39     if(l>nr||r<nl)//两个区间不重合
 40         return 0; 
 41     else if(l>=nl&&r<=nr)//待查询区间完全含于当前区间,返回当前节点值即可
 42         return val[nde];
 43     else//两个区间部分重合
 44     {
 45         release(nde,l,r);//下放当前节点标记,为在儿子里找答案做准备
 46         int mid=(l+r)/2,ls=2*nde,rs=2*nde+1;
 47         return vquery(ls,l,mid,nl,nr)+vquery(rs,mid+1,r,nl,nr);//在儿子里寻找
 48     }
 49 }
 50 long long mquery(int nde,int l,int r,int nl,int nr)//基本同上
 51 {
 52     if(l>nr||r<nl)
 53         return 0; 
 54     else if(l>=nl&&r<=nr)
 55         return maxx[nde];
 56     else
 57     {
 58         release(nde,l,r);
 59         int mid=(l+r)/2,ls=2*nde,rs=2*nde+1;
 60         return max(mquery(ls,l,mid,nl,nr),mquery(rs,mid+1,r,nl,nr));
 61     }
 62 }
 63 void add(int nde,int l,int r,int nl,int nr,int task)//区间加,task->区间要修改的值
 64 {
 65     if(l>nr||r<nl)
 66         return ;
 67     else if(l>=nl&&r<=nr)
 68         laz1[nde]+=task,maxx[nde]+=task,val[nde]+=task*(r-l+1);
 69     else
 70     {
 71         release(nde,l,r);       
 72         int mid=(l+r)/2,ls=2*nde,rs=2*nde+1;
 73         add(ls,l,mid,nl,nr,task),add(rs,mid+1,r,nl,nr,task);
 74         val[nde]=val[ls]+val[rs],maxx[nde]=max(maxx[ls],maxx[rs]);
 75     }
 76 }
 77 void mul(int nde,int l,int r,int nl,int nr,int task)
 78 {
 79     if(l>nr||r<nl)
 80         return ;
 81      else if(l>=nl&&r<=nr)
 82         val[nde]*=task,maxx[nde]*=task,laz1[nde]*=task,laz2[nde]*=task;
 83     else
 84     {
 85         release(nde,l,r);
 86         int mid=(l+r)/2,ls=2*nde,rs=2*nde+1;
 87            mul(ls,l,mid,nl,nr,task),mul(rs,mid+1,r,nl,nr,task);
 88         val[nde]=val[ls]+val[rs],maxx[nde]=max(maxx[ls],maxx[rs]);
 89     }
 90 }
 91 int main()
 92 {
 93     scanf("%lld%lld",&n,&m);
 94     for(int i=1;i<=n;i++)
 95         scanf("%lld",&num[i]);
 96     create(1,1,n);
 97     for(int i=1;i<=m;i++)
 98     {
 99         scanf("%lld",&ask);
100         if(ask==1)
101             scanf("%lld%lld%lld",&x,&y,&k),mul(1,1,n,x,y,k);
102         else if(ask==2)
103             scanf("%lld%lld%lld",&x,&y,&k),add(1,1,n,x,y,k);   
104         else if(ask==3)
105             scanf("%lld%lld",&x,&y),printf("%lld\n",vquery(1,1,n,x,y));
106         else
107             scanf("%lld%lld",&x,&y),printf("%lld\n",mquery(1,1,n,x,y));
108     }
109     return 0;
110 }
View Code

Upd on 2019.3.26 复习线段树扫描线

HDU1828 线段树扫描线模板题 注意是多组数据(沙茶的洛谷讨论,我居然信了。。。

 1 #include<cmath>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 using namespace std;
 6 const int N=10005,M=80005;
 7 int n,t1,t2,t3,t4,tot,lst,sum,ans;
 8 int uni[N],val[M],len[M],cnt[M],lpt[M],rpt[M];
 9 struct a
10 {
11     int lp,rp,yp,tp;
12 }sca[N];
13 bool cmp(a x,a y)
14 {
15     return x.yp==y.yp?x.tp<y.tp:x.yp<y.yp;
16 }
17 void Clean()
18 {
19     tot=lst=sum=ans=0;
20     memset(uni,0,sizeof uni);
21     memset(val,0,sizeof val);
22     memset(len,0,sizeof len);
23     memset(cnt,0,sizeof cnt);
24     memset(lpt,0,sizeof lpt);
25     memset(rpt,0,sizeof rpt);
26 }
27 void Maintain(int nde,int l,int r)
28 {
29     if(val[nde])
30     {
31         len[nde]=uni[r+1]-uni[l];
32         lpt[nde]=rpt[nde]=cnt[nde]=1;
33     }
34     else
35     {
36         int ls=2*nde,rs=2*nde+1;
37         len[nde]=len[ls]+len[rs];
38         lpt[nde]=lpt[ls],rpt[nde]=rpt[rs];
39         cnt[nde]=cnt[ls]+cnt[rs]-(rpt[ls]&&lpt[rs]);
40     }
41 }
42 void Modify(int nde,int l,int r,int ll,int rr,int tsk)
43 {
44     if(l>rr||r<ll)
45         return;
46     else if(l>=ll&&r<=rr)
47         val[nde]+=tsk,Maintain(nde,l,r);
48     else
49     {
50         int mid=(l+r)>>1,ls=2*nde,rs=2*nde+1;
51         Modify(ls,l,mid,ll,rr,tsk);
52         Modify(rs,mid+1,r,ll,rr,tsk);
53         Maintain(nde,l,r);
54     }
55 }
56 int main()
57 {
58     while(scanf("%d",&n)!=EOF)
59     {
60         Clean();
61         for(int i=1;i<=n;i++)
62         {
63             scanf("%d%d%d%d",&t1,&t2,&t3,&t4);
64             uni[++tot]=t1,sca[tot]=(a){t1,t3,t2,1};
65             uni[++tot]=t3,sca[tot]=(a){t1,t3,t4,-1};
66         }
67         sort(uni+1,uni+1+tot);
68         int lth=unique(uni+1,uni+1+tot)-uni-1;
69         sort(sca+1,sca+1+tot,cmp);
70         for(int i=1;i<=tot;i++)
71         {
72             int ll=lower_bound(uni+1,uni+1+lth,sca[i].lp)-uni;
73             int rr=lower_bound(uni+1,uni+1+lth,sca[i].rp)-uni-1;
74             if(ll<=rr) Modify(1,1,lth,ll,rr,sca[i].tp);
75             ans+=abs(len[1]-lst)+(sca[i+1].yp-sca[i].yp)*cnt[1]*2,lst=len[1];
76         }
77         printf("%d\n",ans);
78     }
79     return 0;
80 }
View Code

TJOI2010污染的河流

线段树pushdown叶子,这人没救了

改改就行了,比上面那个简单

4.ST表

①ST表是啥

一种数据结构,利用了倍增、dp(和分治?)的思想,支持$O(1)$查询区间最值。

②ST表的原理

首先我们抓来一段数列,(对于一个学过高中必修一的人来说,他应该知道)一个长度为$n$的序列有$2^n$个子区间,我们由倍增的思想,规定$ST[i][j]$表示从第i个数起$2^j$个数中的最大值。显然$ST[i][0]=n[i]$。

由此我们开始递推,先计算总体范围:$logg=log(n)/log(2)+1$,其中$logg$是满足$2^{logg}>=n$的最小自然数。然后外层循环$j$从$1$到$logg-1$枚举各个幂,内层循环$i$从$1$到$n+1-2^j$枚举各个小区间,由此我们就枚举了所有的区间。接下来状态转移方程为:

$ST[i][j]=max(ST[i][j-1],ST[i+2^(j-1)][j-1])$

状态转移方程的意义如下:

我们把一个长度为$2^j$的区间拆成两个$2^{j-1}$的小区间,显然有

$ST[i][j]$->当前区间

$ST[i][j-1]$->左侧小区间

$ST[i+2^{(j-1)}][j-1]$->右侧小区间

自然简洁清晰明了(逃

对于查询,仍利用类似的思想,先计算一个$logg'=log$(区间长度)$/log(2)$,这样一来区间左端点开始的$2^{(logg'-1)}$个数和区间右端点结尾的$2^{(logg'-1)}$个数必定覆盖了整个区间,显然查询即

$max(ST[left][logg'],ST[right-(1<<logg')+1][logg'])$

③ST表的时间复杂度与所需空间?

时间复杂度:预处理为 $O(nlog$ $n)$,单次查询$O(1)$

所需空间:$nlog$ $n$

④ST表的代码实现

 1 #include<cmath>
 2 #include<cstdio>
 3 #include<algorithm>
 4 using namespace std;
 5 const int N=100005;
 6 int num[N],ST[N][18],n,m;
 7 int main()
 8 {
 9     int logg,left,right;
10     scanf("%d%d",&n,&m);
11     for(int i=1;i<=n;i++)
12     {
13         scanf("%d",&num[i]);
14         ST[i][0]=num[i];
15     }
16     logg=log(n)/log(2)+1;
17     for(int j=1;j<logg;j++)//枚举,预处理
18         for(int i=1;i<=n-(1<<j)+1;i++)
19             ST[i][j]=max(ST[i][j-1],ST[i+(1<<j-1)][j-1]);
20     for(int i=1;i<=m;i++)
21     {
22         scanf("%d%d",&left,&right);
23         logg=log(right-left+1)/log(2);
24         printf("%d\n",max(ST[left][logg],ST[right-(1<<logg)+1][logg]));//查询
25     }
26     return 0;
27 }
View Code

 

https://www.researchgate.net/profile/Jan_Van_Leeuwen2/publication/220430653_Worst-case_Analysis_of_Set_Union_Algorithms/links/0a85e53cd28bfdf5eb000000/Worst-case-Analysis-of-Set-Union-Algorithms.pdf

posted @ 2018-09-18 11:00  Speranza_Leaf  阅读(151)  评论(0)    收藏  举报