洛谷P4891 序列 || 膜法阵,魔法阵
https://www.luogu.org/problemnew/show/P4891
一道几乎一样的题http://210.33.19.103/contest/1130/problem/3 题面https://files.cnblogs.com/files/hehe54321/2019%E7%9C%81%E9%80%89%E8%81%94%E5%90%88%E8%AE%AD%E7%BB%8310.pdf.zip
(这题看了题解后做的)题解(这个站看上去有点像那种爬虫站,并没有在原博客上找到,可能原博客被删掉了?)
首先,设C中关键点为C[i]!=C[i-1]的i(1也是关键点)
整个C序列被这些关键点划分成了一些段,段内部C值都是相等的
每一次修改A都越改越大,因此会删掉一些关键点,然后加入O(1)个关键点(大概理解一下。。);那么,总共加入关键点的数量是O(n+q)级别的,总共删除关键点的数量也是O(n+q)级别的
设一个势能函数,函数的值是序列中有多少个位置的C<B
修改A的时候,由于任何位置的C都不会越改越小,势能函数会减少这样一个值:这一次修改过程中有多少个位置由C<B变为C>=B
每一次单点修改B,最多使得势能函数增加1
因此,可以设计一个算法,使得修改A的时候,复杂度与“这一次修改过程中有多少个位置由C<B变为C>=B”有关,这样复杂度就是对的
每一次修改A的时候,相当于对于C做一些区间修改(根据上面的结论,所有操作的区间修改总次数是O(n+q)的;要找到这个修改的区间的话,可以用另外一个数据结构维护;貌似为了减小代码复杂度,这个也可以直接在下面线段树的基础上实现,就每个节点多记一个区间内A的最大值ma)
每次区间修改,大概可以这样用线段树维护一下:
线段树每个节点存:
区间内所有C<B的位置的C的积a1;区间内C<B的位置的个数n1;
区间内所有C>=B的位置的B的积a2;区间内C>=B的位置的个数n2;
区间内所有C<B的位置的B的最小值mi(没有则设为INF);
区间修改的懒标记;
这样的话,区间修改C的时候,dfs线段树(设要将区间[l,r]内的C都改为y);首先,只走与[l,r]交集不为空的区间;走到不被[l,r]包含的区间的时候直接递归下去,回溯的时候更新节点信息;走到被[l,r]包含的区间的时候,当且仅当它的mi<=y时,说明区间内存在点由C<B变为C>=B,继续递归下去,回溯的时候更新节点信息;否则打个懒标记就走,不要递归下去
这样的复杂度就是$O((n+q)log^2n)$(第二个log是由于区间修改打懒标记以及pushdown的时候更新a1需要快速幂)
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #include<vector> 5 #include<set> 6 using namespace std; 7 #define fi first 8 #define se second 9 #define mp make_pair 10 #define pb push_back 11 typedef long long ll; 12 typedef unsigned long long ull; 13 const int md=1000000007; 14 int poww(int a,int b) 15 { 16 int ans=1; 17 for(;b;b>>=1,a=ull(a)*a%md) 18 if(b&1) 19 ans=ull(ans)*a%md; 20 return ans; 21 } 22 int a[100011],b[100011],c[100011]; 23 int n,qq; 24 namespace S 25 { 26 struct Nd 27 { 28 int a1,n1; 29 int a2,n2; 30 int mi,ma; 31 int tag;//-1表示没有lazytag 32 }d[400011]; 33 #define LC (u<<1) 34 #define RC (u<<1|1) 35 void doSet(int u,int y) 36 { 37 d[u].tag=y; 38 d[u].a1=poww(y,d[u].n1); 39 } 40 void pd(int u) 41 { 42 if(d[u].tag!=-1) 43 { 44 doSet(LC,d[u].tag); 45 doSet(RC,d[u].tag); 46 d[u].tag=-1; 47 } 48 } 49 void upd(int u) 50 { 51 d[u].a1=ull(d[LC].a1)*d[RC].a1%md; 52 d[u].n1=d[LC].n1+d[RC].n1; 53 d[u].a2=ull(d[LC].a2)*d[RC].a2%md; 54 d[u].n2=d[LC].n2+d[RC].n2; 55 d[u].mi=min(d[LC].mi,d[RC].mi); 56 d[u].ma=max(d[LC].ma,d[RC].ma); 57 } 58 void build(int l,int r,int u) 59 { 60 d[u].tag=-1; 61 if(l==r) 62 { 63 if(c[l]<b[l]) 64 { 65 d[u].a1=c[l];d[u].n1=1; 66 d[u].a2=1; 67 d[u].mi=b[l]; 68 } 69 else 70 { 71 d[u].a1=1; 72 d[u].a2=b[l];d[u].n2=1; 73 d[u].mi=0x3f3f3f3f; 74 } 75 d[u].ma=a[l]; 76 return; 77 } 78 int mid=(l+r)>>1; 79 build(l,mid,LC);build(mid+1,r,RC); 80 upd(u); 81 } 82 void changea(int L,int x,int l,int r,int u) 83 { 84 if(l==r) {d[u].ma=x;return;} 85 pd(u); 86 int mid=(l+r)>>1; 87 if(L<=mid) changea(L,x,l,mid,LC); 88 else changea(L,x,mid+1,r,RC); 89 upd(u); 90 } 91 int find1(int x,int l,int r,int u)//找到最靠左的位置使得C值>x,不存在则返回r+1 92 { 93 if(d[u].ma<=x) return r+1; 94 int pa=-1; 95 while(l!=r) 96 { 97 int mid=(l+r)>>1; 98 if(max(d[LC].ma,pa)>x) r=mid,u=LC; 99 else pa=max(pa,d[LC].ma),l=mid+1,u=RC; 100 } 101 return l; 102 } 103 int queryc0(int L,int l,int r,int u)//返回用ma计算出的C值 104 { 105 int ans=-1; 106 while(l!=r) 107 { 108 int mid=(l+r)>>1; 109 if(L<=mid) r=mid,u=LC; 110 else ans=max(ans,d[LC].ma),l=mid+1,u=RC; 111 } 112 return max(ans,d[u].ma); 113 } 114 void changec(int L,int R,int x,int l,int r,int u) 115 { 116 if(r<L || R<l) return; 117 if(L<=l && r<=R && d[u].mi>x) 118 { 119 doSet(u,x); 120 return; 121 } 122 if(l==r) 123 { 124 d[u].a1=1;d[u].n1=0; 125 d[u].a2=b[l];d[u].n2=1; 126 d[u].mi=0x3f3f3f3f; 127 return; 128 } 129 pd(u); 130 int mid=(l+r)>>1; 131 changec(L,R,x,l,mid,LC); 132 changec(L,R,x,mid+1,r,RC); 133 upd(u); 134 } 135 void changeb(int L,int x,int c,int l,int r,int u) 136 { 137 if(l==r) 138 { 139 c=max(c,d[u].ma); 140 if(c<x) 141 { 142 d[u].a1=c;d[u].n1=1; 143 d[u].a2=1;d[u].n2=0; 144 d[u].mi=x; 145 } 146 else 147 { 148 d[u].a1=1;d[u].n1=0; 149 d[u].a2=x;d[u].n2=1; 150 d[u].mi=0x3f3f3f3f; 151 } 152 return; 153 } 154 pd(u); 155 int mid=(l+r)>>1; 156 if(L<=mid) changeb(L,x,c,l,mid,LC); 157 else changeb(L,x,max(c,d[LC].ma),mid+1,r,RC); 158 upd(u); 159 } 160 } 161 int main() 162 { 163 int i,j,idx,x,y,t; 164 scanf("%d%d",&n,&qq); 165 for(i=1;i<=n;++i) 166 { 167 scanf("%d",a+i); 168 c[i]=i==1?a[i]:max(a[i],c[i-1]); 169 } 170 for(i=1;i<=n;++i) 171 scanf("%d",b+i); 172 S::build(1,n,1); 173 while(qq--) 174 { 175 scanf("%d%d%d",&idx,&x,&y); 176 if(idx==0) 177 { 178 t=S::queryc0(x,1,n,1); 179 S::changea(x,y,1,n,1); 180 if(y<=t) goto xx1; 181 t=S::find1(y,1,n,1)-1; 182 S::changec(x,t,y,1,n,1); 183 } 184 else 185 { 186 b[x]=y; 187 S::changeb(x,y,-1,1,n,1); 188 } 189 xx1:; 190 printf("%llu\n",ull(S::d[1].a1)*S::d[1].a2%md); 191 } 192 return 0; 193 }
原题解的备份,来自https://www.twblogs.net/a/5baa45a32b717750855c8b75/zh-cn,博客https://www.cnblogs.com/flashhu/
闲话
考场上一眼看出这是个毒瘤线段树准备杠题,发现实在太难调了,被各路神犇虐哭qwq
考后看到各种优雅的暴力AC。。。。。。宝宝心里苦qwq
思路分析
题面里面是一堆乱七八糟的限制和性质,这时候需要冷静想想有没有可利用的地方。蒟蒻一开始往势能线段树上面想了想。
定义一个全局势能函数,为所有\(C_i<B_i\)的位置个数。注意两个操作的修改都不会小于原来的数。
一个是改\(A\),相当于对\(C\)进行区间设置,此时我们每暴力找到一个原来\(C_i<B_i\)但是现在\(C_i\ge y\)的位置,就需要在线段树内跳\(\log\)层,再修改,势能函数就会下降。如果碰到那些修改以后对势能没有影响的子区间,就跳过而不继续暴力递归下去。
一个是改\(B\),是单点修改,线段树内跳\(\log\)层,势能函数至多加\(1\)。
于是,如果我们能够维护信息,从而判断和控制哪里该暴力递归、哪里该跳过的话,我们总的在线段树内跳的次数不会超过\((n+q)\log n\)。
下面用a代替了\(C\),b代替了\(B\)。蒟蒻在线段树里维护了:
- pa:区间所有a<b的位置的a的积
- pb:区间所有a>=b的位置的b的积
- ma:区间a的最大值
- mb:区间所有a<b的位置的b的最小值,没有则设成INF
- cnt:区间所有a<b的位置的总数
- la:bool型变量,区间设置懒标记
修改a的时候,利用单调不降的性质,我们在线段树上先通过二分来对需要修改的若干个子树进行定位。对于当前子区间,如果当前设置值\(y\)比mb要小,那么设置对答案没有影响,直接打上区间设置标记后退出;否则继续递归直到找到叶子节点,进行修改后退出。
修改b就比较轻松,只要找到对应的叶子节点改完后一路回溯即可。
很多事都是说起来容易做起来难,这题也不例外。调试几乎花了整个晚上。要注意的细节很多,也只好自己仔细思考了。
时间复杂度\(O(n\log n+q\log^2n)\),上界很松。多出来的\(\log\)是区间改a时需要快速幂更新pa造成的。
跑了不到200ms,比什么树套树、分块还是要好看一点,但是被暴力碾压也是有点无奈啊~
1 #include<bits/stdc++.h> 2 #define RG register 3 #define R RG int 4 #define I inline 5 #define G if(++ip==ie)fread(ip=buf,1,N,stdin) 6 using namespace std; 7 typedef long long LL; 8 const LL N=1<<18,YL=1e9+7; 9 char buf[N],*ie=buf+N,*ip=ie-1; 10 int y,a[N]; 11 I int in(){ 12 G;while(*ip<'-')G; 13 R x=*ip&15;G; 14 while(*ip>'-'){x*=10;x+=*ip&15;G;} 15 return x; 16 } 17 I LL qpow(RG LL b,R k){//快速幂 18 RG LL a=1; 19 for(;k;k>>=1,(b*=b)%=YL) 20 if(k&1)(a*=b)%=YL; 21 return a; 22 } 23 struct Node{//个人认为写指针比较直观(貌似immortalCO也有这样的看法) 24 Node*lc,*rc;bool la; 25 LL pa,pb;int l,r,m,ma,mb,cnt; 26 I void up(){//上传 27 pa=lc->pa*rc->pa%YL; 28 pb=lc->pb*rc->pb%YL; 29 ma=max(lc->ma,rc->ma); 30 mb=min(lc->mb,rc->mb); 31 cnt=lc->cnt+rc->cnt; 32 } 33 I void dn(){//下传区间设置标记 34 if(la){ 35 lc->ma=rc->ma=ma; 36 lc->la=rc->la=1;la=0; 37 lc->pa=qpow(ma,lc->cnt); 38 rc->pa=qpow(ma,rc->cnt); 39 } 40 } 41 I void build(R s,R e){//建树 42 l=s;r=e;m=(s+e)>>1;la=0; 43 if(s==e){ 44 ma=a[l];mb=in(); 45 (cnt=ma<mb)?(pa=ma,pb=1):(pa=1,pb=mb,mb=YL); 46 return; 47 } 48 (lc=new Node)->build(l,m); 49 (rc=new Node)->build(m+1,r); 50 this->up(); 51 } 52 I void upda(){//区间修改a 53 if(y<mb){//对区间势能没有影响 54 pa=qpow(ma=y,cnt);la=1; 55 return; 56 } 57 if(l==r){//到了叶子节点 58 ma=y;pa=1;pb=mb;mb=YL;cnt=0; 59 return; 60 } 61 this->dn(); 62 lc->upda();rc->upda(); 63 this->up(); 64 } 65 I void updb(R s){//单点更新b 66 if(l==r){//仔细判断三种情况再修改 67 if(ma<pb)mb=y; 68 else if(ma<y)pa=ma,pb=cnt=1,mb=y; 69 else pb=y; 70 return; 71 } 72 this->dn(); 73 (s<=m?lc:rc)->updb(s); 74 this->up(); 75 } 76 I void bound(R s){//线段树二分定位,注意细节 77 if(s==l&&ma<y)return this->upda(); 78 if(l==r)return; 79 this->dn(); 80 if(s>m)return rc->bound(s),this->up(); 81 if(lc->ma<y)rc->bound(m+1); 82 lc->bound(s); 83 this->up(); 84 } 85 }; 86 int main(){ 87 R n=in(),q=in(),op,x; 88 for(R i=1;i<=n;++i)a[i]=max(a[i-1],in()); 89 RG Node rt;rt.build(1,n); 90 while(q--){ 91 op=in();x=in();y=in(); 92 op?rt.updb(x):rt.bound(x); 93 printf("%lld\n",rt.pa*rt.pb%YL); 94 } 95 return 0; 96 }