模板汇总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 }
·按秩合并单次操作$O(\log n)$,路径压缩单次操作$O(\log_{1+m/n} n)$,路径压缩+按秩合并单次操作$O(α(m,n))$
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 }
·性质
引自李煜东的蓝书
将树状数组视作一个树形结构,它满足如下性质:
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 }
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 }
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 }
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

浙公网安备 33010602011771号