线段树
今天才发现博客里竟然没有叫做线段树的文章欸。(主席树 $\in$ 线段树)
线段树是一种支持区间操作的数据结构...定义什么的就不说了吧。
有的题目只是需要用到线段树优化,而关键的难度不在这里,这类的题目其实做过不少了,但是都比较水,相对比较难的就是那道“列队”了:https://www.cnblogs.com/shzr/p/9484250.html
以下题目尽量按难易度排序,然而不保证递增qwq。
线段树可以维护的信息合集:(不断更新中)
1.区间加,区间求和。
2.区间加,区间乘,区间求和;
模板1(区间加,区间求和):https://www.luogu.org/problemnew/show/P3372
其实也可以拿树状数组做,这里粘的是好久以前写的,码风比较奇怪.
1 // luogu-judger-enable-o2 2 # include3 # include 4 5 using namespace std; 6 7 const int maxn=400050; 8 9 int m,n; 10 long long a[100004]={0}; 11 long long s[maxn]={0},lazy[maxn]={0}; 12 13 void update(int now) 14 { 15 s[now]=s[now<<1]+s[(now<<1)+1]; 16 } 17 18 void build (int now,int l,int r) 19 { 20 if (l==r) 21 { 22 s[now]=a[l]; 23 return; 24 } 25 int mid=(l+r)>>1; 26 build(now<<1,l,mid); 27 build((now<<1)+1,mid+1,r); 28 update(now); 29 } 30 31 void pushdown(int now,int l,int r) 32 { 33 if(lazy[now]) 34 { 35 int mid=(l+r)>>1; 36 s[now<<1]+=lazy[now]*(mid-l+1); 37 lazy[now<<1]+=lazy[now]; 38 s[(now<<1)+1]+=lazy[now]*(r-mid); 39 lazy[(now<<1)+1]+=lazy[now]; 40 lazy[now]=0; 41 } 42 } 43 44 long long ask(int now,int l,int r,int lrange,int rrange) 45 { 46 if(lrange<=l&&r<=rrange) 47 return s[now]; 48 pushdown(now,l,r); 49 int mid=(l+r)>>1; 50 long long ans=0; 51 if (lrange<=mid) ans+=ask(now<<1,l,mid,lrange,rrange); 52 if (rrange>mid) ans+=ask((now<<1)+1,mid+1,r,lrange,rrange); 53 return ans; 54 } 55 56 void change(int now,int l,int r,int lrange,int rrange,long long v) 57 { 58 if(lrange<=l&&r<=rrange) 59 { 60 s[now]+=(r-l+1)*v; 61 lazy[now]+=v; 62 return; 63 } 64 pushdown(now,l,r); 65 int mid=(l+r)>>1; 66 if(lrange<=mid) 67 change(now<<1,l,mid,lrange,rrange,v); 68 if(rrange>mid) 69 change((now<<1)+1,mid+1,r,lrange,rrange,v); 70 update(now); 71 } 72 73 int main() 74 { 75 int op; 76 int x,y; 77 long long k; 78 scanf("%d%d",&n,&m); 79 for (int i=1;i<=n;i++) 80 scanf("%lld",&a[i]); 81 build(1,1,n); 82 for (int i=1;i<=m;i++) 83 { 84 scanf("%d",&op); 85 if (op==1) 86 { 87 scanf("%d%d%lld",&x,&y,&k); 88 change(1,1,n,x,y,k); 89 } 90 if (op==2) 91 { 92 scanf("%d%d",&x,&y); 93 cout< 1,1,n,x,y)<<endl; 94 } 95 } 96 return 0; 97 }
模板2:(区间加,区间乘,区间求和):https://www.luogu.org/problemnew/show/P3373
这道题其实不算特别难,但是引出了线段树类型中的一系列重要问题:标记的下放顺序,互相之间的影响,初始化;在这道题里有乘法和加法两个标记,每次有乘法标记时应当将加法标记乘上它。乘法标记的空状态应当为$1$.
1 // luogu-judger-enable-o2 2 # include3 # include 4 # include 5 # define LL long long 6 # define nl n<<1 7 # define nr n<<1|1 8 9 using namespace std; 10 11 const int MAXN=1000008; 12 13 int n,m; 14 int x1,f1; 15 LL x2,f2; 16 char c; 17 LL P; 18 LL a[MAXN]={0}; 19 LL lazy_c[MAXN*4]={0},s[MAXN*4]={0},lazy_j[MAXN*4]={0}; 20 21 void update(int n) 22 { 23 s[n]=(s[nl]+s[nr])%P; 24 } 25 26 void pushdown_c(int n,int l,int r) 27 { 28 int mid=(l+r)>>1; 29 lazy_j[nl]=(lazy_j[nl]*lazy_c[n])%P; 30 lazy_j[nr]=(lazy_j[nr]*lazy_c[n])%P; 31 lazy_c[nl]=(lazy_c[nl]*lazy_c[n])%P; 32 lazy_c[nr]=(lazy_c[nr]*lazy_c[n])%P; 33 s[nl]=(s[nl]*lazy_c[n])%P; 34 s[nr]=(s[nr]*lazy_c[n])%P; 35 lazy_c[n]=1; 36 }; 37 38 void pushdown_j(int n,int l,int r) 39 { 40 int mid=(l+r)>>1; 41 lazy_j[n]%=P; 42 s[nl]=(s[nl]+lazy_j[n]*(mid-l+1))%P; 43 lazy_j[nl]=(lazy_j[nl]+lazy_j[n])%P; 44 s[nr]=(s[nr]+lazy_j[n]*(r-mid))%P; 45 lazy_j[nr]=(lazy_j[nr]+lazy_j[n])%P; 46 lazy_j[n]=0; 47 } 48 49 50 void build(int now,int l,int r) 51 { 52 if(l==r) 53 { 54 s[now]=a[l]; 55 lazy_c[now]=1; 56 return; 57 } 58 int mid=(l+r)>>1; 59 build(now<<1,l,mid); 60 build((now<<1)+1,mid+1,r); 61 lazy_c[now]=1; 62 update(now); 63 } 64 65 void addc(int now,int l,int r,int ll,int rr,long long k) 66 { 67 k=k%P; 68 if(ll<=l&&r<=rr) 69 { 70 lazy_c[now]=(lazy_c[now]*k)%P; 71 lazy_j[now]=(lazy_j[now]*k)%P; 72 s[now]=(s[now]*k)%P; 73 return; 74 } 75 pushdown_c(now,l,r); 76 if (lazy_j[now]) pushdown_j(now,l,r); 77 int mid=(l+r)>>1; 78 if(ll<=mid) addc(now<<1,l,mid,ll,rr,k); 79 if(rr>mid) addc((now<<1)+1,mid+1,r,ll,rr,k); 80 update(now); 81 return; 82 } 83 84 void addj(int now,int l,int r,int ll,int rr,long long k) 85 { 86 k=k%P; 87 if(ll<=l&&r<=rr) 88 { 89 s[now]=(k*(r-l+1)+s[now])%P; 90 lazy_j[now]=(k+lazy_j[now])%P; 91 return ; 92 } 93 pushdown_c(now,l,r); 94 if (lazy_j[now]) pushdown_j(now,l,r); 95 int mid=(l+r)>>1; 96 if(ll<=mid) addj(now<<1,l,mid,ll,rr,k); 97 if(rr>mid) addj((now<<1)+1,mid+1,r,ll,rr,k); 98 update(now); 99 return; 100 } 101 102 LL ask(int now,int l,int r,int ll,int rr) 103 { 104 if(ll<=l&&r<=rr) return s[now]%P; 105 LL ans=0; 106 pushdown_c(now,l,r); 107 if (lazy_j[now]) pushdown_j(now,l,r); 108 int mid=(l+r)>>1; 109 if(ll<=mid) ans+=ask(now<<1,l,mid,ll,rr); 110 ans=ans%P; 111 if(rr>mid) ans+=ask(now<<1|1,mid+1,r,ll,rr); 112 return ans%P; 113 } 114 115 int readd() 116 { 117 x1=0; f1=1; 118 c=getchar(); 119 while (!isdigit(c)) 120 { 121 if(c=='-') f1=-f1; 122 c=getchar(); 123 } 124 while (isdigit(c)) 125 { 126 x1=(x1<<3)+(x1<<1)+(c^48); 127 c=getchar(); 128 } 129 return f1*x1; 130 } 131 132 long long readdd() 133 { 134 x2=0; f2=1; 135 c=getchar(); 136 while (!isdigit(c)) 137 { 138 if(c=='-') f2=-f2; 139 c=getchar(); 140 } 141 while (isdigit(c)) 142 { 143 x2=(x2<<3)+(x2<<1)+(c^48); 144 c=getchar(); 145 } 146 return f2*x2; 147 } 148 149 int main() 150 { 151 int x,x1,y; 152 LL k; 153 n=readd(); 154 m=readd(); 155 P=readdd(); 156 for (register int i=1;i<=n;i++) 157 a[i]=readdd(); 158 build(1,1,n); 159 for (register int i=1;i<=m;i++) 160 { 161 x=readd(); 162 if(x==1) 163 { 164 x1=readd(); 165 y=readd(); 166 k=readdd(); 167 addc(1,1,n,x1,y,k); 168 } 169 if(x==2) 170 { 171 x1=readd(); 172 y=readd(); 173 k=readdd(); 174 addj(1,1,n,x1,y,k); 175 } 176 if(x==3) 177 { 178 x1=readd(); 179 y=readd(); 180 printf("%lld\n",ask(1,1,n,x1,y)); 181 } 182 } 183 return 0; 184 }
降雨量:https://www.lydsy.com/JudgeOnline/problem.php?id=1067
题意概述:给出$n$个年份的降雨量,再给出$m$个询问,形如:$x$年是$y$年以来降雨最多的,也就是说:$x$年的降雨量不超过$y$年,且对于任意$y
这真是一道有名的题目啊!https://www.lydsy.com/JudgeOnline/wttl/thread.php?tid=1108
其实这个题挺简单的,就是细节比较多.首先年份可以非常大,但是年份的数量还可以接受,所以考虑离散化,将询问和给出的一起离散化,将知道的信息插入一棵线段树,每次询问即可.
即可.
这当然是不可能的,否则不就太简单了吗.
离散化的时候不能普普通通的离散化,要插入一些空的年份,为什么?举个栗子:知道 $2018$ 年的降雨量是 $10$ , $2016$ 年的是 $12$ ,中间的那些年份都不清楚,如果只是简简单单的进行离散化,$2016$和$2018$就成了相邻的两个年份,本来应该是$maybe$的答案就成了$true$...所以离散化时如果两个年份不相邻就应当插入一个空的年份,如果已经相邻了就不能再插入了.
关于判断:这里要充分利用题目给出的每一个条件,比如说:读入一个询问后发现$X$年根本就是未知的,于是想当然的以为这种情况没法判断,就输出$maybe$,然后就$WA$了.为什么?因为$Z
1 // luogu-judger-enable-o2 2 # include3 # include 4 # include 5 # include 6 # define R register int 7 # define nl (n<<1) 8 # define nr (n<<1|1) 9 10 using namespace std; 11 12 const int maxn=50004; 13 const int maxm=10004; 14 const int maxt=maxn+maxn+maxm+maxm; 15 int n,m,cnt,ans,maxx; 16 int v[maxt],vy,vn; 17 int y[maxn],r[maxn]; 18 int X[maxm],Y[maxm]; 19 int t[maxt<<2],h; 20 bool c[maxt<<2],f; 21 struct dig 22 { 23 int val,id,n_or_m; 24 }a[maxn+maxm+maxm]; 25 26 bool cmp (dig a,dig b) 27 { 28 return a.val<b.val; 29 } 30 31 void ins (int n,int l,int r,int pos,int val) 32 { 33 if(l==r) 34 { 35 t[n]=val; 36 c[n]=false; 37 return; 38 } 39 int mid=(l+r)>>1; 40 if(pos<=mid) ins(nl,l,mid,pos,val); 41 else ins(nr,mid+1,r,pos,val); 42 t[n]=max(t[nl],t[nr]); 43 c[n]=c[nl]|c[nr]; 44 } 45 46 int ask (int n,int l,int r,int ll,int rr) 47 { 48 if(ll<=l&&r<=rr) 49 { 50 if(c[n]) f=true; 51 return t[n]; 52 } 53 int mid=(l+r)>>1,ans=0; 54 if(ll<=mid) ans=max(ans,ask(nl,l,mid,ll,rr)); 55 if(rr>mid) ans=max(ans,ask(nr,mid+1,r,ll,rr)); 56 return ans; 57 } 58 59 int main() 60 { 61 scanf("%d",&n); 62 for (R i=1;i<=n;++i) 63 { 64 scanf("%d%d",&y[i],&r[i]); 65 a[++h].val=y[i]; 66 a[h].id=i; 67 a[h].n_or_m=1; 68 } 69 scanf("%d",&m); 70 for (R i=1;i<=m;++i) 71 { 72 scanf("%d%d",&Y[i],&X[i]); 73 a[++h].val=X[i]; 74 a[h].id=i; 75 a[h].n_or_m=2; 76 a[++h].val=Y[i]; 77 a[h].id=i; 78 a[h].n_or_m=3; 79 } 80 sort(a+1,a+1+h,cmp); 81 a[0].val=a[1].val-1; 82 for (R i=1;i<=h;++i) 83 { 84 if(a[i].val!=a[i-1].val) 85 { 86 if(a[i].val!=a[i-1].val+1) cnt++; 87 cnt++; 88 } 89 if(a[i].n_or_m==1) y[ a[i].id ]=cnt; 90 else if(a[i].n_or_m==2) X[ a[i].id ]=cnt; 91 else Y[ a[i].id ]=cnt; 92 } 93 n=cnt; 94 memset(c,true,sizeof(c)); 95 memset(v,-1,sizeof(v)); 96 for (R i=1;i<=n;++i) 97 ins(1,1,n,y[i],r[i]),v[ y[i] ]=r[i]; 98 for (R i=1;i<=m;++i) 99 { 100 f=false; 101 ans=0; 102 vy=v[ Y[i] ]; 103 vn=v[ X[i] ]; 104 if(vy 1) { printf("false\n"); continue; } 105 if(Y[i]+1>X[i]-1) 106 { 107 if(vy==-1||vn==-1) printf("maybe\n"); 108 else printf("true\n"); 109 continue; 110 } 111 maxx=ask(1,1,n,Y[i]+1,X[i]-1); 112 if(vy!=-1&&vn==-1&&maxx+1>vy) { printf("false\n"); continue; } 113 if(maxx>=vn&&vn!=-1) { printf("false\n"); continue; } 114 if(vn!=-1&&maxx false&&vy!=-1) { printf("true\n"); continue; } 115 printf("maybe\n"); 116 } 117 return 0; 118 }
区间交:https://www.51nod.com/Challenge/Problem.html#!#problemId=1672
题意概述:给出一个长度为$n$,元素非负的整数序列,再给出$m$个闭区间,从中选出$k$个求交,得到的得分是交部分元素的和,求得分的最大值.$n,m,k<=10^5$
因为元素非负,那么如果固定了开头,多选一些至少不会更劣,知道这一点就好做了.
首先将区间按照左端点排序,从头开始加,用线段树维护每个点被多少个区间覆盖了,由于固定了左端点,这个值也是单调不增的,所以二分看从每一个点开始最长能延伸到那里,更新最优解即可.不过因为只有区间加和单点询问,用树状数组会更好一点.
1 # include2 # include 3 # include 4 # define ll long long 5 # define R register int 6 # define lowbit(i) (i&(-i)) 7 8 using namespace std; 9 10 const int maxn=100005; 11 int h,t,n,k,m,c[maxn]; 12 ll s[maxn],ans; 13 struct Interval 14 { 15 int l,r; 16 }q[maxn]; 17 18 bool cmp (Interval a,Interval b) 19 { 20 if(a.l==b.l) return a.r<b.r; 21 return a.l<b.l; 22 } 23 24 void add (int l,int r,int v) 25 { 26 r++; 27 for (R i=l;i<=n;i+=lowbit(i)) c[i]+=v; 28 for (R i=r;i<=n;i+=lowbit(i)) c[i]-=v; 29 } 30 31 int ask (int x) 32 { 33 int ans=0; 34 for (R i=x;i;i-=lowbit(i)) ans+=c[i]; 35 return ans; 36 } 37 38 int f (int x) 39 { 40 int l=x,r=n,mid,ans=0; 41 while(l<=r) 42 { 43 mid=(l+r)>>1; 44 if(ask(mid)>=k) 45 ans=mid,l=mid+1; 46 else 47 r=mid-1; 48 } 49 return ans; 50 } 51 52 int main() 53 { 54 scanf("%d%d%d",&n,&k,&m); 55 for (R i=1;i<=n;++i) 56 scanf("%lld",&s[i]),s[i]+=s[i-1]; 57 for (R i=1;i<=m;++i) 58 scanf("%d%d",&q[i].l,&q[i].r); 59 sort(q+1,q+1+m,cmp); 60 h=1,t=0; 61 for (R i=1;i<=n;++i) 62 { 63 while (q[t+1].l<=i&&t+1<=m) add(q[t+1].l,q[t+1].r,1),t=t+1; 64 while (q[h].r1),h=h+1; 65 if(t-h+1 continue; 66 ans=max(ans,(s[ f(i) ]-s[i-1])); 67 } 68 printf("%lld",ans); 69 return 0; 70 }
序列操作:https://www.lydsy.com/JudgeOnline/problem.php?id=1858
题意概述:给出长度为n的01序列,要求支持以下操作,m次:$1<=n, m<=100000$
区间覆盖0/1;
区间取反;
区间求和;
区间求最长连续1;
除了难写一点好像也没有什么别的难度.就是...确实难写.
这些操作单独支持都不难,但是带上取反后的最长连续一怎么做呢?很简单,对于以前需要保留的关于1的信息,同时存一份0的用来备份,区间取反时将两份信息调换。
注意:区间取反会将区间覆盖的标记取反,而区间覆盖可以直接覆盖区间取反。
Update欣赏:
pushdown欣赏:
1 # include2 # include 3 # include 4 # include <string> 5 # define R register int 6 # define nl (n<<1) 7 # define nr (n<<1|1) 8 9 using namespace std; 10 11 const int maxn=100005; 12 int n,m,opt,l,r; 13 char st[4]; 14 struct node 15 { 16 int s,ans1,lans1,rans1,ans2,lans2,rans2,delta,siz; 17 }t[maxn<<2],ans; 18 19 char ss[1<<17],*A=ss,*B=ss; 20 inline char gc() 21 { 22 if(A==B) 23 { 24 B=(A=ss)+fread(ss,1,1<<17,stdin); 25 if(A==B)return EOF; 26 } 27 return *A++; 28 } 29 template<class T>inline void read(T&x) 30 { 31 char c; 32 int y=1; 33 while(c=gc(),c<48||57<c) 34 if(c=='-')y=-1;x=c^48; 35 while(c=gc(),47 58) 36 x=(x<<1)+(x<<3)+(c^48); 37 x*=y; 38 } 39 40 void out (int x) 41 { 42 if(x>=10) out(x/10); 43 putchar(x%10+'0'); 44 } 45 46 node update (node a,node b) 47 { 48 node ans; 49 ans.s=a.s+b.s; 50 ans.ans1=max(a.ans1,max(b.ans1,a.rans1+b.lans1)); 51 ans.lans1=a.lans1; 52 if(a.s==a.siz) ans.lans1=max(ans.lans1,a.s+b.lans1); 53 ans.rans1=b.rans1; 54 if(b.s==b.siz) ans.rans1=max(ans.rans1,b.s+a.rans1); 55 ans.ans2=max(a.ans2,max(b.ans2,a.rans2+b.lans2)); 56 ans.lans2=a.lans2; 57 if(a.s==0) ans.lans2=max(ans.lans2,a.siz+b.lans2); 58 ans.rans2=b.rans2; 59 if(b.s==0) ans.rans2=max(ans.rans2,b.siz+a.rans2); 60 ans.siz=a.siz+b.siz; 61 ans.delta=-1; 62 return ans; 63 } 64 65 void build (int n,int l,int r) 66 { 67 if(l==r) 68 { 69 int x=0; 70 read(x); 71 if(x==1) 72 { 73 t[n].s=t[n].ans1=t[n].lans1=t[n].rans1=1; 74 t[n].ans2=t[n].lans2=t[n].rans2=0; 75 } 76 else 77 { 78 t[n].s=t[n].ans1=t[n].lans1=t[n].rans1=0; 79 t[n].ans2=t[n].lans2=t[n].rans2=1; 80 } 81 t[n].delta=-1; 82 t[n].siz=1; 83 return ; 84 } 85 int mid=(l+r)>>1; 86 build(nl,l,mid); 87 build(nr,mid+1,r); 88 t[n]=update(t[nl],t[nr]); 89 } 90 91 void pushdown (int n,int l,int r) 92 { 93 int x=t[n].delta; 94 t[n].delta=-1; 95 if(x==2) 96 { 97 t[nl].s=t[nl].siz-t[nl].s; 98 if(t[nl].delta==-1) t[nl].delta=2; 99 else if(t[nl].delta==2) t[nl].delta=-1; 100 else t[nl].delta^=1; 101 swap(t[nl].ans1,t[nl].ans2); 102 swap(t[nl].lans1,t[nl].lans2); 103 swap(t[nl].rans1,t[nl].rans2); 104 t[nr].s=t[nr].siz-t[nr].s; 105 if(t[nr].delta==-1) t[nr].delta=2; 106 else if(t[nr].delta==2) t[nr].delta=-1; 107 else t[nr].delta^=1; 108 swap(t[nr].ans1,t[nr].ans2); 109 swap(t[nr].lans1,t[nr].lans2); 110 swap(t[nr].rans1,t[nr].rans2); 111 } 112 else if(x==1) 113 { 114 t[nl].delta=x; 115 t[nr].delta=x; 116 t[nl].s=t[nl].siz; 117 t[nl].ans1=t[nl].lans1=t[nl].rans1=t[nl].siz; 118 t[nl].ans2=t[nl].lans2=t[nl].rans2=0; 119 t[nr].s=t[nr].siz; 120 t[nr].ans1=t[nr].lans1=t[nr].rans1=t[nr].siz; 121 t[nr].ans2=t[nr].lans2=t[nr].rans2=0; 122 } 123 else 124 { 125 t[nl].delta=x; 126 t[nr].delta=x; 127 t[nl].s=0; 128 t[nl].ans1=t[nl].lans1=t[nl].rans1=0; 129 t[nl].ans2=t[nl].lans2=t[nl].rans2=t[nl].siz; 130 t[nr].s=0; 131 t[nr].ans1=t[nr].lans1=t[nr].rans1=0; 132 t[nr].ans2=t[nr].lans2=t[nr].rans2=t[nr].siz; 133 } 134 } 135 136 void cov (int n,int l,int r,int ll,int rr,int col) 137 { 138 if(ll<=l&&r<=rr) 139 { 140 t[n].delta=col; 141 if(col==1) 142 { 143 t[n].s=t[n].siz; 144 t[n].ans1=t[n].lans1=t[n].rans1=t[n].siz; 145 t[n].ans2=t[n].lans2=t[n].rans2=0; 146 } 147 else 148 { 149 t[n].s=0; 150 t[n].ans1=t[n].lans1=t[n].rans1=0; 151 t[n].ans2=t[n].lans2=t[n].rans2=t[n].siz; 152 } 153 return ; 154 } 155 int mid=(l+r)>>1; 156 if(t[n].delta!=-1) pushdown(n,l,r); 157 if(ll<=mid) cov(nl,l,mid,ll,rr,col); 158 if(rr>mid) cov(nr,mid+1,r,ll,rr,col); 159 t[n]=update(t[nl],t[nr]); 160 } 161 162 void chan (int n,int l,int r,int ll,int rr) 163 { 164 if(ll<=l&&r<=rr) 165 { 166 t[n].s=t[n].siz-t[n].s; 167 if(t[n].delta==-1) t[n].delta=2; 168 else if(t[n].delta==2) t[n].delta=-1; 169 else t[n].delta^=1; 170 swap(t[n].ans1,t[n].ans2); 171 swap(t[n].lans1,t[n].lans2); 172 swap(t[n].rans1,t[n].rans2); 173 return ; 174 } 175 int mid=(l+r)>>1; 176 if(t[n].delta!=-1) pushdown(n,l,r); 177 if(ll<=mid) chan(nl,l,mid,ll,rr); 178 if(rr>mid) chan(nr,mid+1,r,ll,rr); 179 t[n]=update(t[nl],t[nr]); 180 } 181 182 int ask (int n,int l,int r,int ll,int rr) 183 { 184 if(ll<=l&&r<=rr) return t[n].s; 185 int ans=0,mid=(l+r)>>1; 186 if(t[n].delta!=-1) pushdown(n,l,r); 187 if(ll<=mid) ans+=ask(nl,l,mid,ll,rr); 188 if(rr>mid) ans+=ask(nr,mid+1,r,ll,rr); 189 return ans; 190 } 191 192 node cont (int n,int l,int r,int ll,int rr) 193 { 194 if(ll<=l&&r<=rr) return t[n]; 195 int mid=(l+r)>>1; 196 if(t[n].delta!=-1) pushdown(n,l,r); 197 if(rr<=mid) return cont(nl,l,mid,ll,rr); 198 else if(ll>mid) return cont(nr,mid+1,r,ll,rr); 199 return update(cont(nl,l,mid,ll,rr),cont(nr,mid+1,r,ll,rr)); 200 } 201 202 int main() 203 { 204 read(n),read(m); 205 build(1,1,n); 206 for (R i=1;i<=m;++i) 207 { 208 read(opt); 209 read(l); 210 read(r); 211 l++,r++; 212 if(opt==0) 213 cov(1,1,n,l,r,0); 214 else if(opt==1) 215 cov(1,1,n,l,r,1); 216 else if(opt==2) 217 chan(1,1,n,l,r); 218 else if(opt==3) 219 out(ask(1,1,n,l,r)),putchar('\n'); 220 else if(opt==4) 221 { 222 ans=cont(1,1,n,l,r); 223 out(ans.ans1); 224 putchar('\n'); 225 } 226 } 227 return 0; 228 }
色版游戏:https://www.luogu.org/problemnew/show/P1558
题意概述:维护一个长度为$n$的序列,要求支持区间覆盖,区间询问有几种不同颜色.$n<=10^6$,颜色总数不超过30.
线段树的每个节点保留一个二进制数,表示这些颜色在这一段出现过.说这一道题的目的是不要思维僵化,线段树能存的信息很多.甚至还可以每个节点再存一个数据结构.
1 // luogu-judger-enable-o2 2 # include3 # include 4 # define R register int 5 # define nl n<<1 6 # define nr n<<1|1 7 8 using namespace std; 9 10 const int maxn=100009; 11 int xx,cc,n,t,o,l,r,co,s; 12 long long c[maxn<<2],delta[maxn<<2],ans; 13 char ch; 14 15 void build(int n,int l,int r) 16 { 17 delta[n]=-1; 18 if(l==r) 19 { 20 c[n]=1; 21 return ; 22 } 23 int mid=(l+r)>>1; 24 build(nl,l,mid); 25 build(nr,mid+1,r); 26 c[n]=c[nl]|c[nr]; 27 } 28 29 void pushdown(int n,int l,int r) 30 { 31 int x=delta[n]; 32 delta[n]=-1; 33 delta[nl]=x; 34 delta[nr]=x; 35 c[nl]=(1<<(x-1)); 36 c[nr]=(1<<(x-1)); 37 c[n]=c[nl]|c[nr]; 38 } 39 40 void chan (int n,int l,int r,int ll,int rr,int co) 41 { 42 if(ll<=l&&r<=rr) 43 { 44 delta[n]=co; 45 c[n]=(1<<(co-1)); 46 return ; 47 } 48 if(delta[n]!=-1) pushdown(n,l,r); 49 int mid=(l+r)>>1; 50 if(ll<=mid)chan(nl,l,mid,ll,rr,co); 51 if(rr>mid)chan(nr,mid+1,r,ll,rr,co); 52 c[n]=c[nl]|c[nr]; 53 } 54 55 long long ask (int n,int l,int r,int ll,int rr) 56 { 57 if(ll<=l&&r<=rr) 58 return c[n]; 59 if(delta[n]!=-1) pushdown(n,l,r); 60 int mid=(l+r)>>1; 61 long long ans=0; 62 if(ll<=mid) ans=ans|ask(nl,l,mid,ll,rr); 63 if(rr>mid) ans=ans|ask(nr,mid+1,r,ll,rr); 64 return ans; 65 } 66 67 int read() 68 { 69 xx=0; 70 cc=getchar(); 71 while (!isdigit(cc)) cc=getchar(); 72 while (isdigit(cc)) xx=(xx<<3)+(xx<<1)+(cc^48),cc=getchar(); 73 return xx; 74 } 75 76 int main() 77 { 78 n=read(),t=read(),o=read(); 79 build(1,1,n); 80 for (R i=1;i<=o;i++) 81 { 82 ch=getchar(); 83 while (ch!='P'&&ch!='C') ch=getchar(); 84 if(ch=='C') 85 { 86 l=read(),r=read(),co=read(); 87 if(l>r) swap(l,r); 88 chan(1,1,n,l,r,co); 89 } 90 else 91 { 92 l=read(),r=read(); 93 if(l>r) swap(l,r); 94 ans=ask(1,1,n,l,r); 95 s=0; 96 for (int i=0;i<=t;i++) 97 if (ans&(1<; 98 printf("%d\n",s); 99 } 100 } 101 return 0; 102 }
方差:https://www.luogu.org/problemnew/show/P1471
题意概述:区间加,区间求平均数,区间求方差.
见到这么一道题,首先考虑将方差的公式拆开,变的可以用线段树维护.
$s^2=\frac{\sum_{i=1}^n(x_i-\overline{x})^2}{n}$
$n\times s^2=\sum_{i=1}^n(x_i-\overline{x})^2$
$=\sum_{i=1}^n(x_i^2-2x_i\overline{x}+\overline{x}^2)$
$=\sum_{i=1}^nx_i^2+2 \overline{x}\sum_{i=1}^nx_i+n\times \overline{x}^2$
其实到这里好像还是可以再化一下的,但是反正也要问平均数,我选择先查询一次平均数,再到线段树上算刚刚那个式子.每个节点维护区间和以及区间平方和.
这里还有一个区间加,对于区间和可以直接加,平方和就把完全平方公式拆开来用好了.注意:更新平方和时还要用到区间和,这里的区间和应该是修改以前的,所以先改平方和再改普通和.
1 // luogu-judger-enable-o2 2 # include3 # include 4 # include 5 # define nl n<<1 6 # define nr n<<1|1 7 # define R register int 8 9 using namespace std; 10 11 const int maxn=100005; 12 int n,m,ll,rr; 13 double a[maxn]={0}; 14 double delta[maxn*4]={0},s[maxn*4]={0},ss[maxn*4]={0}; 15 16 inline void update(int n) 17 { 18 s[n]=s[nl]+s[nr]; 19 ss[n]=ss[nl]+ss[nr]; 20 } 21 22 void build(int n,int l,int r) 23 { 24 if(l==r) 25 { 26 s[n]=a[l]; 27 ss[n]=a[l]*a[l]; 28 return; 29 } 30 31 int mid=(l+r)>>1; 32 build(nl,l,mid); 33 build(nr,mid+1,r); 34 update(n); 35 } 36 37 void pushdown(int n,int l,int r) 38 { 39 double x=delta[n]; 40 int mid=(l+r)>>1; 41 delta[nl]+=x; 42 delta[nr]+=x; 43 ss[nl]+=(mid-l+1)*x*x+2*s[nl]*x; 44 ss[nr]+=(r-mid)*x*x+2*s[nr]*x; 45 s[nl]+=x*(mid-l+1); 46 s[nr]+=x*(r-mid); 47 delta[n]=0; 48 } 49 50 double ask(int n,int l,int r,int ll,int rr) 51 { 52 if(ll<=l&&r<=rr) 53 return s[n]; 54 55 if(delta[n]) pushdown(n,l,r); 56 int mid=(l+r)>>1; 57 double ans=0; 58 if(ll<=mid) ans+=ask(nl,l,mid,ll,rr); 59 if(rr>mid) ans+=ask(nr,mid+1,r,ll,rr); 60 return ans; 61 } 62 63 double askk(int n,int l,int r,int ll,int rr,double avg) 64 { 65 if(ll<=l&&r<=rr) 66 return (ss[n]+(r-l+1)*avg*avg-2*avg*s[n]); 67 if(delta[n]) pushdown(n,l,r); 68 int mid=(l+r)>>1; 69 double ans=0; 70 if(ll<=mid) ans+=askk(nl,l,mid,ll,rr,avg); 71 if(rr>mid) ans+=askk(nr,mid+1,r,ll,rr,avg); 72 return ans; 73 } 74 75 void add(int n,int l,int r,int ll,int rr,double x) 76 { 77 if(ll<=l&&r<=rr) 78 { 79 delta[n]+=x; 80 ss[n]+=x*x*(r-l+1)+2*x*s[n]; 81 s[n]+=(r-l+1)*x; 82 return ; 83 } 84 pushdown(n,l,r); 85 int mid=(l+r)>>1; 86 if(ll<=mid) add(nl,l,mid,ll,rr,x); 87 if(rr>mid) add(nr,mid+1,r,ll,rr,x); 88 update(n); 89 return ; 90 } 91 92 int main() 93 { 94 scanf("%d%d",&n,&m); 95 int op;//指示剂 96 double k; 97 for (R i=1;i<=n;i++) 98 scanf("%lf",&a[i]); 99 build(1,1,n); 100 for (R i=1;i<=m;i++) 101 { 102 scanf("%d",&op); 103 if(op==1) 104 { 105 scanf("%d%d",&ll,&rr); 106 cin>>k; 107 add(1,1,n,ll,rr,k); 108 } 109 else if(op==2) 110 { 111 scanf("%d%d",&ll,&rr); 112 printf("%0.4lf\n",ask(1,1,n,ll,rr)/(double)(rr-ll+1)); 113 } 114 else 115 { 116 scanf("%d%d",&ll,&rr); 117 double avg=ask(1,1,n,ll,rr); 118 avg=avg/(double)(rr-ll+1); 119 printf("%0.4lf\n",askk(1,1,n,ll,rr,avg)/(double)(rr-ll+1)); 120 } 121 } 122 return 0; 123 }
小白逛公园:https://www.luogu.org/problemnew/show/P4513
题意概述:单点修改,区间最大连续子段和.
每个区间维护以下信息:最大子段和,从左端点开始的最大子段和,从右开始的最大子段和,区间总和.
上传时:
最大子段和为:(左儿子最大子段和,右儿子最大子段和,左儿子从右端点的最大子段和+右儿子从左端点的最大子段和)取最大值.
1 # include2 # include b; return a; } 9 const int maxn=500009; 10 int k,x,y,n,m,a[maxn]; 11 struct nod 12 { 13 int lm,rm,ans,sum; 14 }t[maxn<<2],ans; 15 16 nod update (nod a,nod b) 17 { 18 nod ans; 19 ans.sum=a.sum+b.sum; 20 ans.lm=max(a.lm,a.sum+b.lm); 21 ans.rm=max(b.rm,b.sum+a.rm); 22 ans.ans=max(a.ans,max(b.ans,a.rm+b.lm)); 23 return ans; 24 } 25 26 void build (int n,int l,int r) 27 { 28 if(l==r) 29 { 30 t[n].ans=a[l]; 31 t[n].lm=a[l]; 32 t[n].rm=a[l]; 33 t[n].sum=a[l]; 34 return ; 35 } 36 int mid=(l+r)>>1; 37 build(nl,l,mid); 38 build(nr,mid+1,r); 39 t[n]=update(t[nl],t[nr]); 40 } 41 42 int read() 43 { 44 int x=0,f=1; 45 char c=getchar(); 46 while (!isdigit(c)) 47 { 48 if(c=='-') f=-f; 49 c=getchar(); 50 } 51 while (isdigit(c)) 52 { 53 x=(x<<3)+(x<<1)+(c^48); 54 c=getchar(); 55 } 56 return x*f; 57 } 58 59 void change (int n,int l,int r,int pos,int v) 60 { 61 if(l==pos&&pos==r) 62 { 63 t[n].ans=v; 64 t[n].lm=v; 65 t[n].rm=v; 66 t[n].sum=v; 67 return ; 68 } 69 int mid=(l+r)>>1; 70 if(pos<=mid) change(nl,l,mid,pos,v); 71 if(pos>mid) change(nr,mid+1,r,pos,v); 72 t[n]=update(t[nl],t[nr]); 73 } 74 75 nod ask(int n,int l,int r,int ll,int rr) 76 { 77 if(ll<=l&&r<=rr) 78 return t[n]; 79 int mid=(l+r)>>1; 80 nod a,b; 81 a.ans=a.lm=a.rm=a.sum=0; 82 b.ans=b.lm=b.rm=b.sum=0; 83 if(ll<=mid) a=ask(nl,l,mid,ll,rr); 84 if(rr>mid) b=ask(nr,mid+1,r,ll,rr); 85 if(ll<=mid&&rr>mid) return update(a,b); 86 if(ll<=mid&&rr<=mid) return a; 87 return b; 88 } 89 90 int main() 91 { 92 n=read(),m=read(); 93 for (R i=1;i<=n;++i) 94 a[i]=read(); 95 build(1,1,n); 96 for (R i=1;i<=m;++i) 97 { 98 k=read(),x=read(),y=read(); 99 if(k==1) 100 { 101 if(x>y) std::swap(x,y); 102 ans=ask(1,1,n,x,y); 103 printf("%d\n",ans.ans); 104 } 105 else 106 change(1,1,n,x,y); 107 } 108 return 0; 109 }3 # define R register int 4 # define nl (n<<1) 5 # define nr (n<<1|1) 6 7 int min (int a,int b) { if(a>b) return b; return a; } 8 int max (int a,int b) { if(areturn
排序:https://lydsy.com/JudgeOnline/problem.php?id=4552
题意概述:给出一个$1-n$的全排列,进行$m$次局部排序,最后询问一次第$q$个位置上的数.$n,m<=50000$
这道题好神奇啊,完全看不出和线段树有什么关系,线段树还可以排序吗?其实是可以的,把思路放宽一点,如果只有$0,1$序列,显然是可以用线段树排序的,也就是说,如果想用线段树进行排序,必须要借助桶排的思想才行.对于这道题,可以发现只询问一次,而且可以先把这个询问位置读进来再进行排序操作.考虑二分,将大于等于二分值的数设为1,否则为0,用线段树进行一番排序,如果这时$q$为1,说明这个位置上的数至少是二分值.写起来不大难,但是这个思想还是挺妙的.
1 # include2 # include 3 # define nl (n<<1) 4 # define nr (n<<1|1) 5 6 using namespace std; 7 8 const int maxn=200005; 9 int n,m,a[maxn],t[maxn<<2],delta[maxn<<2],l[maxn],r[maxn],op[maxn],L,R,mid,ans,pos; 10 11 void build (int n,int l,int r,int base) 12 { 13 if(l==r) 14 { 15 if(a[l]>=base) t[n]=1; 16 else t[n]=0; 17 delta[n]=-1; 18 return; 19 } 20 int mid=(l+r)>>1; 21 build(nl,l,mid,base); 22 build(nr,mid+1,r,base); 23 t[n]=t[nl]+t[nr]; 24 delta[n]=-1; 25 } 26 27 void pushdown (int n,int l,int r) 28 { 29 int mid=(l+r)>>1; 30 delta[nl]=delta[n]; 31 delta[nr]=delta[n]; 32 if(delta[n]==1) t[nl]=mid-l+1; 33 if(delta[n]==1) t[nr]=r-mid; 34 if(delta[n]==0) t[nl]=0; 35 if(delta[n]==0) t[nr]=0; 36 delta[n]=-1; 37 } 38 39 40 int ask (int n,int l,int r,int ll,int rr) 41 { 42 if(ll<=l&&r<=rr) return t[n]; 43 int mid=(l+r)>>1,ans=0; 44 if(delta[n]!=-1) pushdown(n,l,r); 45 if(ll<=mid) ans+=ask(nl,l,mid,ll,rr); 46 if(rr>mid) ans+=ask(nr,mid+1,r,ll,rr); 47 return ans; 48 } 49 50 void change (int n,int l,int r,int ll,int rr,int col) 51 { 52 if(ll<=l&&r<=rr) 53 { 54 delta[n]=col; 55 if(col==1) t[n]=r-l+1; 56 else t[n]=0; 57 return ; 58 } 59 int mid=(l+r)>>1; 60 if(delta[n]!=-1) pushdown(n,l,r); 61 if(ll<=mid) change(nl,l,mid,ll,rr,col); 62 if(rr>mid) change(nr,mid+1,r,ll,rr,col); 63 t[n]=t[nl]+t[nr]; 64 } 65 66 bool check (int x) 67 { 68 build(1,1,n,x); 69 int cnt; 70 for (register int i=1;i<=m;++i) 71 { 72 cnt=ask(1,1,n,l[i],r[i]); 73 if(cnt==0) continue; 74 change(1,1,n,l[i],r[i],0); 75 if(op[i]) change(1,1,n,l[i],l[i]+cnt-1,1); 76 else change(1,1,n,r[i]-cnt+1,r[i],1); 77 } 78 return ask(1,1,n,pos,pos)==1; 79 } 80 81 inline int read() 82 { 83 register int x=0; 84 register char c=getchar(); 85 while (!isdigit(c)) c=getchar(); 86 while (isdigit(c)) { x=(x<<3)+(x<<1)+(c^48); c=getchar(); } 87 return x; 88 } 89 90 int main() 91 { 92 scanf("%d%d",&n,&m); 93 for (register int i=1;i<=n;++i) 94 a[i]=read(); 95 for (register int i=1;i<=m;++i) 96 { 97 op[i]=read(),l[i]=read(),r[i]=read(); 98 if(l[i]>r[i]) swap(l[i],r[i]); 99 } 100 L=1,R=n,ans=1; 101 scanf("%d",&pos); 102 while (L<=R) 103 { 104 mid=(L+R)>>1; 105 if(check(mid)) ans=max(ans,mid),L=mid+1; 106 else R=mid-1; 107 } 108 printf("%d",ans); 109 return 0; 110 }
无聊的数列:https://www.luogu.org/problemnew/show/P1438
题意概述:区间加等差数列,单点查询.$n,m<=10^5$
等差数列的性质:差分后所有数均相等,所以可以转化为区间加.单点查询时就查询前缀和即可.
1 # include2 # include 3 # define nl n<<1 4 # define nr n<<1|1 5 # define R register int 6 7 using namespace std; 8 9 const int N=100079; 10 11 int m,n,l,r,k,d,x,rx,rf; 12 char rc; 13 long long ans=0; 14 int c[N<<2]={0},a[N]={0},delta[N<<2]={0}; 15 16 inline int readd() 17 { 18 rx=0; 19 rf=1; 20 rc=getchar(); 21 while (!isdigit(rc)) 22 { 23 if(rc=='-') rf=-rf; 24 rc=getchar(); 25 } 26 while (isdigit(rc)) 27 { 28 rx=(rx<<3)+(rx<<1)+(rc^48); 29 rc=getchar(); 30 } 31 return rx*rf; 32 } 33 34 void build(int n,int l,int r) 35 { 36 delta[n]=0; 37 if(l==r) 38 { 39 c[n]=a[r]; 40 return; 41 } 42 int mid=(l+r)>>1; 43 build(nl,l,mid); 44 build(nr,mid+1,r); 45 c[n]=c[nl]+c[nr]; 46 } 47 48 void pushdown(int n,int l,int r) 49 { 50 if(delta[n]) 51 { 52 int mid=(l+r)>>1; 53 int x=delta[n]; 54 delta[n<<1]+=x; 55 delta[n<<1|1]+=x; 56 delta[n]=0; 57 c[nl]+=(mid-l+1)*x; 58 c[nr]+=(r-mid)*x; 59 } 60 } 61 62 63 long long ask(int n,int l,int r,int ll,int rr) 64 { 65 if(ll>r||rr return 0; 66 if(ll<=l&&r<=rr) 67 return c[n]; 68 pushdown(n,l,r); 69 int mid=(l+r)>>1; 70 return ask(nl,l,mid,ll,rr)+ask(nr,mid+1,r,ll,rr); 71 } 72 73 void add(int n,int l,int r,int ll,int rr,int x) 74 { 75 if(ll>r||rr return ; 76 if(ll<=l&&r<=rr) 77 { 78 delta[n]+=x; 79 c[n]+=(r-l+1)*x; 80 return ; 81 } 82 pushdown(n,l,r); 83 int mid=(l+r)>>1; 84 add(nl,l,mid,ll,rr,x); 85 add(nr,mid+1,r,ll,rr,x); 86 c[n]=c[nl]+c[nr]; 87 } 88 89 int main() 90 { 91 n=readd(); 92 m=readd(); 93 l=0; 94 for (R i=1;i<=n;i++) 95 { 96 x=readd(); 97 a[i]=x-l; 98 l=x; 99 } 100 a[n+1]=l-x; 101 build(1,1,n); 102 for (R i=1;i<=m;i++) 103 { 104 x=readd(); 105 x--; 106 if(x) //查询 107 { 108 scanf("%d",&l); 109 ans=ask(1,1,n,1,l); 110 printf("%lld\n",ans); 111 } 112 else //加 113 { 114 scanf("%d%d%d%d",&l,&r,&k,&d); 115 add(1,1,n,l+1,r,d); 116 add(1,1,n,l,l,k); 117 add(1,1,n,r+1,r+1,-k-(r-l)*d); 118 } 119 } 120 return 0; 121 }
花神游历各国:https://www.luogu.org/problemnew/show/P4145
题意概述:区间开平方并下取整,区间求和.$n,m<=10^5,a_i<=10^{12}$
看起来好像很玄妙,其实很简单,因为每个数不会变大,只会不停地被开方,而即使每个数都达到值域上界,开上六次平方也就成1了,所以维护每个区间的最大值,如果不是1或0就暴力开方,否则就不用开了.
1 # include2 # include 3 # include 4 # define nl (n<<1) 5 # define nr (n<<1|1) 6 # define R register int 7 8 using namespace std; 9 10 const int maxn=100005; 11 int n,m,k,l,r; 12 long long t[maxn<<2],s[maxn<<2]; 13 14 void build (int n,int l,int r) 15 { 16 if(l==r) 17 { 18 scanf("%lld",&s[n]); 19 t[n]=s[n]; 20 return; 21 } 22 int mid=(l+r)>>1; 23 build(nl,l,mid); 24 build(nr,mid+1,r); 25 s[n]=s[nl]+s[nr]; 26 t[n]=max(t[nl],t[nr]); 27 } 28 29 long long ask (int n,int l,int r,int ll,int rr) 30 { 31 if(ll<=l&&r<=rr) 32 return s[n]; 33 int mid=(l+r)>>1; 34 long long ans=0; 35 if(ll<=mid) ans+=ask(nl,l,mid,ll,rr); 36 if(rr>mid) ans+=ask(nr,mid+1,r,ll,rr); 37 return ans; 38 } 39 40 void change (int n,int l,int r,int ll,int rr) 41 { 42 if(ll<=l&&r<=rr&&t[n]<=1) 43 return; 44 if(l==r) 45 { 46 s[n]=(long long)sqrt(s[n]); 47 t[n]=s[n]; 48 return; 49 } 50 int mid=(l+r)>>1; 51 if(ll<=mid) change(nl,l,mid,ll,rr); 52 if(rr>mid) change(nr,mid+1,r,ll,rr); 53 s[n]=s[nl]+s[nr]; 54 t[n]=max(t[nl],t[nr]); 55 } 56 57 int read() 58 { 59 int x=0; 60 char c=getchar(); 61 while (!isdigit(c)) c=getchar(); 62 while (isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar(); 63 return x; 64 } 65 66 int main() 67 { 68 scanf("%d",&n); 69 build(1,1,n); 70 scanf("%d",&m); 71 for (R i=1;i<=m;++i) 72 { 73 k=read(),l=read(),r=read(); 74 if(l>r) swap(l,r); 75 if(k==1) printf("%lld\n",ask(1,1,n,l,r)); 76 else change(1,1,n,l,r); 77 } 78 return 0; 79 }
HH的项链:https://www.luogu.org/problemnew/show/P1972
题意概述:求区间内不同元素数量,多组询问,资瓷离线。$n,m<=5 \times 10^5$
方法一:莫队。莫队做法非常显然,但是可能会T;
方法二:树状数组。考虑右端点不断向右移动,对于每种颜色就可以只考虑最右边的那个,而左边的再也不会用到了。利用这个性质,每次向右移动时,之前那个扔掉,加上新的,这个过程用树状数组来实现(一个位置如果是一,表示这里出现了一个(对于目前的右端点)后面没有再出现过的颜色)所以区间的和就是答案。询问按照右端点排序,离线做。这个做法可以很方便的推广,即使用可持久化数据结构如主席树来代替树状数组,保留以每个点为右端点时的信息。
方法三:二维数点。将每个数字视为一个点(x,pre[x](上次出现位置)),对于每个询问求满足以下条件的点的数目(l<=x<=r,0<=pre[x]
贪婪大陆:https://www.luogu.org/problemnew/show/P2184
康娜的线段树:https://www.luogu.org/problemnew/show/P3924
梦美与线段树:https://www.luogu.org/problemnew/show/P4927
CF893F:https://www.luogu.org/problemnew/show/CF893F
题意概述:给定一棵树,多组询问,求以 $x$ 节点为根的子树中与它距离不超过 $k$ 的点的点权最小值,强制在线。$n,m<=10^6$
一道挺有趣的题目,先想一下离线有什么好做法?好像没有...可以dsu on tree做到两个log。
直接在线吧。一个比较简单的想法是变成一种广义上的二维数点,给一个点二维权值 $(a,b)$,分别表示深度和dfs序。那么,每次要统计的就是:
$min\{v(a,b)|dep_x<=a<=dep_x+k,id_x<=b<=id_x+siz_x-1\}$
这好像很难做啊。简化一下,如果是求和,怎么做?可以将 $a$ 作为一维,$b$ 作为一维,用主席树来做。但这种方法要求所求的值具有可减性,所以好像不能做。
但其实是可以的,想一想,如果只是查前缀某某,是不是就不需要具有可减性了?这道题恰好就有这样的性质。
可以发现,虽然要数的点似乎有四条限制,但是一个点的子树内的点不可能比他的深度还小,所以第一条性质加了等于白加...即:
$min\{v(a,b)|a<=dep_x+k,id_x<=b<=id_x+siz_x-1\}$
这样就可以做了,将 $a$ 作为主席树的第一维,只需要查询一段前缀区间...感觉说的有点不明不白,总之还是看式子理解一下吧。这个做法一个log,还挺巧妙的。代码没有。
---shzr