线段树测试-2019.3.10
线段树专题测试-2019.03.10
又名:对拍的100种写挂方法。
窗口的星星:https://www.luogu.org/problemnew/show/P1502
其实挺简单的...发现窗框不算,所以想到将窗口大小横纵都减2,但是这样是错误的,因为窗口放置的位置可以是实数。只要不是两边都有星星卡在边界上,稍微挪动一下窗口就可以都看到了,减1即可。
首先离散化一下,枚举下边界,维护以每个点作为左边界时可以看到的星星数,求个最大值就可以了。因为一颗星星可以被看见的左端点是一段连续的区间,所以用线段树来做。
1 // luogu-judger-enable-o2 2 # include <cstdio> 3 # include <iostream> 4 # include <algorithm> 5 # include <cstring> 6 # define R register int 7 # define LL long long 8 # define nl (n<<1) 9 # define nr (n<<1|1) 10 11 using namespace std; 12 13 const int maxn=10005; 14 int n,w,h,cnt,v[maxn],lef[maxn],T; 15 LL ans=0,t[maxn<<2],d[maxn<<2]; 16 struct stars { int x,y,c; }a[maxn]; 17 struct num { int v,id; }c[maxn]; 18 19 bool cmpa (stars a,stars b) 20 { 21 if(a.y==b.y) return a.x<b.x; 22 return a.y<b.y; 23 } 24 25 bool cmpc (num a,num b) { return a.v<b.v; } 26 27 void pushdown (int n) 28 { 29 LL x=d[n]; 30 d[n]=0; 31 d[nl]+=x; t[nl]+=x; 32 d[nr]+=x; t[nr]+=x; 33 } 34 35 void add (int n,int l,int r,int ll,int rr,int c) 36 { 37 if(ll<=l&&r<=rr) 38 { 39 t[n]+=c; 40 d[n]+=c; 41 return ; 42 } 43 int mid=(l+r)>>1; 44 if(d[n]!=0) pushdown(n); 45 if(ll<=mid) add(nl,l,mid,ll,rr,c); 46 if(rr>mid) add(nr,mid+1,r,ll,rr,c); 47 t[n]=max(t[nl],t[nr]); 48 } 49 50 int main() 51 { 52 scanf("%d",&T); 53 while(T--) 54 { 55 scanf("%d%d%d",&n,&w,&h); 56 w--,h--; 57 for (R i=1;i<=n;++i) 58 { 59 scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].c); 60 c[i].id=i,c[i].v=a[i].x; 61 } 62 sort(c+1,c+1+n,cmpc); 63 cnt=0; 64 for (R i=1;i<=n;++i) 65 { 66 if(i==1||c[i].v!=c[i-1].v) cnt++; 67 a[ c[i].id ].x=cnt; 68 v[cnt]=c[i].v; 69 } 70 int lw=1; 71 for (R i=1;i<=cnt;++i) 72 { 73 while(v[i]-v[lw]>w) lw++; 74 lef[i]=lw; 75 } 76 sort(a+1,a+1+n,cmpa); 77 ans=0; 78 int l=1,r=0,no; 79 memset(t,0,sizeof(t)); 80 memset(d,0,sizeof(d)); 81 while(r<n) 82 { 83 r++; 84 no=a[r].y; 85 while(r<=n&&a[r].y==no) add(1,1,cnt,lef[ a[r].x ],a[r].x,a[r].c),r++; r--; 86 while(a[r].y-a[l].y>h) add(1,1,cnt,lef[ a[l].x ],a[l].x,-a[l].c),l++; 87 ans=max(ans,t[1]); 88 } 89 printf("%lld\n",ans); 90 } 91 return 0; 92 }
矩形周长:https://www.luogu.org/problemnew/show/P1856
应该说比刚刚那个题还要简单了,甚至不用离散化。先水平扫一遍再竖直扫一遍,看到每条矩形的第一条边就在这个区间+1,否则-1。每次统计被覆盖至少一次的线段长度,与上一次的值做差,求绝对值,加起来就是答案。一开始想得有点复杂,还想维护每个区间仅被覆盖一次的点的数量,后来才发现不用。首先不用下传标记,因为删除的必然是以前加入过的,所以每次修改的区间中的点一起被覆盖的次数都是一样的。更新答案时,如果这个区间被整体覆盖的次数大于0,那么显然就是它的长度,否则就是两儿子的长度和。
1 // luogu-judger-enable-o2 2 # include <cstdio> 3 # include <iostream> 4 # include <algorithm> 5 # include <cstring> 6 # define R register int 7 # define LL long long 8 # define nl (n<<1) 9 # define nr (n<<1|1) 10 11 using namespace std; 12 13 const int maxn=20050; 14 const int z=10005; 15 int n,t[maxn<<3],f[maxn<<3],cnt=20020,a[maxn],b[maxn],c[maxn],d[maxn]; 16 LL ans=0; 17 struct lin { int x,a,b,v; }l[maxn]; 18 19 bool cmp (lin a,lin b) 20 { 21 if(a.x==b.x) return a.v>b.v; 22 return a.x>b.x; 23 } 24 25 int ab (int x) { if(x<0) return -x; return x; } 26 27 void add (int n,int l,int r,int ll,int rr,int c) 28 { 29 if(ll<=l&&r<=rr) 30 { 31 f[n]+=c; 32 if(f[n]==0) t[n]=t[nl]+t[nr]; 33 else t[n]=r-l+1; 34 return ; 35 } 36 int mid=(l+r)>>1; 37 if(ll<=mid) add(nl,l,mid,ll,rr,c); 38 if(rr>mid) add(nr,mid+1,r,ll,rr,c); 39 if(f[n]==0) 40 t[n]=t[nl]+t[nr]; 41 else 42 t[n]=r-l+1; 43 } 44 45 int main() 46 { 47 scanf("%d",&n); 48 for (R i=1;i<=n;++i) 49 { 50 scanf("%d%d%d%d",&a[i],&b[i],&c[i],&d[i]); 51 a[i]+=z,b[i]+=z,c[i]+=z,d[i]+=z; 52 } 53 for (R i=1;i<=n;++i) 54 { 55 l[2*i-1].x=b[i]; 56 l[2*i-1].a=a[i]; 57 l[2*i-1].b=c[i]; 58 l[2*i-1].v=-1; 59 l[2*i].x=d[i]; 60 l[2*i].a=a[i]; 61 l[2*i].b=c[i]; 62 l[2*i].v=1; 63 } 64 sort(l+1,l+1+2*n,cmp); 65 int las=0; 66 for (R i=1;i<=2*n;++i) 67 { 68 add(1,1,cnt,l[i].a,l[i].b-1,l[i].v); 69 ans+=ab(las-t[1]); las=t[1]; 70 } 71 memset(t,0,sizeof(t)); 72 memset(f,0,sizeof(f)); 73 las=0; 74 for (R i=1;i<=n;++i) 75 { 76 l[2*i-1].x=a[i]; 77 l[2*i-1].a=b[i]; 78 l[2*i-1].b=d[i]; 79 l[2*i-1].v=-1; 80 l[2*i].x=c[i]; 81 l[2*i].a=b[i]; 82 l[2*i].b=d[i]; 83 l[2*i].v=1; 84 } 85 sort(l+1,l+1+2*n,cmp); 86 for (R i=1;i<=2*n;++i) 87 { 88 add(1,1,cnt,l[i].a,l[i].b-1,l[i].v); 89 ans+=ab(las-t[1]); las=t[1]; 90 } 91 printf("%lld",ans); 92 return 0; 93 }
TET-Tetris 3D:https://www.luogu.org/problemnew/show/P3437
首先考虑一下一维上的问题,需要一棵带标记的线段树维护。二维上稍微有点麻烦,如果只是简单的推广,会发现答案偏小。比如修改时修改了[1,2]这个区间的[2,3]区域,在查询[1,4]这个区间的[2,3]区域时是查不到答案的(显然不能每次更新都线段树合并)。这里有一个比较巧妙的思路,修改时,将所有与修改区间相交的区间的最大值进行更改,但不更改标记。被修改区间包含的那些区间仅更改标记。查询时,查询相交区间的标记和包含区间的值。正确性显然,可能只是一时间想不到,还是二维线段树做的太少了(根本就没做过好吧...)
1 // luogu-judger-enable-o2 2 # include <cstdio> 3 # include <iostream> 4 # include <algorithm> 5 # include <cstring> 6 # define R register int 7 # define LL long long 8 # define nl (n<<1) 9 # define nr (n<<1|1) 10 11 using namespace std; 12 13 const int maxn=1005; 14 int D,S,n,a,b,c,x,y; 15 struct tre 16 { 17 int t[maxn*3+5],d[maxn*3+5]; 18 int ask (int n,int l,int r,int ll,int rr) 19 { 20 if(ll<=l&&r<=rr) 21 return t[n]; 22 int mid=(l+r)>>1,ans=0; 23 if(ll<=mid) ans=max(ans,ask(nl,l,mid,ll,rr)); 24 if(rr>mid) ans=max(ans,ask(nr,mid+1,r,ll,rr)); 25 return max(ans,d[n]); 26 } 27 void cov (int n,int l,int r,int ll,int rr,int c) 28 { 29 t[n]=max(t[n],c); 30 if(ll<=l&&r<=rr) 31 { 32 d[n]=max(d[n],c); 33 return; 34 } 35 int mid=(l+r)>>1; 36 if(ll<=mid) cov(nl,l,mid,ll,rr,c); 37 if(rr>mid) cov(nr,mid+1,r,ll,rr,c); 38 } 39 }t[maxn*3+5],d[maxn*3+5]; 40 41 int ask (int n,int l,int r,int ll,int rr,int a,int b) 42 { 43 if(ll<=l&&r<=rr) return t[n].ask(1,1,S,a,b); 44 int mid=(l+r)>>1,ans=d[n].ask(1,1,S,a,b); 45 if(ll<=mid) ans=max(ans,ask(nl,l,mid,ll,rr,a,b)); 46 if(rr>mid) ans=max(ans,ask(nr,mid+1,r,ll,rr,a,b)); 47 return ans; 48 } 49 50 void cov (int n,int l,int r,int ll,int rr,int a,int b,int h) 51 { 52 t[n].cov(1,1,S,a,b,h); 53 if(ll<=l&&r<=rr) d[n].cov(1,1,S,a,b,h); 54 else 55 { 56 int mid=(l+r)>>1; 57 if(ll<=mid) cov(nl,l,mid,ll,rr,a,b,h); 58 if(rr>mid) cov(nr,mid+1,r,ll,rr,a,b,h); 59 } 60 } 61 62 int main() 63 { 64 scanf("%d%d%d",&D,&S,&n); 65 int h=0; 66 for (R i=1;i<=n;++i) 67 { 68 scanf("%d%d%d%d%d",&x,&y,&c,&a,&b); 69 x=a+x,y=b+y; 70 a++,b++; 71 h=0; 72 h=ask(1,1,D,a,x,b,y); 73 h+=c; 74 cov(1,1,D,a,x,b,y,h); 75 } 76 int ans=0; 77 ans=ask(1,1,D,1,D,1,S); 78 printf("%d",ans); 79 return 0; 80 }
考试的时候,先写了前两题。这时候好像还很早,所以没有对拍,而是先去开第三题了。结果刚开始看第三题就开始头晕,所以做了很久很久也没想到怎么做,最后写了一个这么一个做法:对于每一行开线段树,每次暴力修改每一棵线段树的相应位置。这时已经没有多少时间了,所以就去写对拍。因为时间不够,对拍写的也很粗糙,第二题的暴力只能处理坐标为正数的情况,所以就只拍了这部分,过了就没管了。然而...负数部分是错的,把负数移至正数的最大值设的太大,所以数组就不够用了,数据又很强,就爆零了。然后写第一题的暴力,怎么拍怎么错,于是抱着凉凉的心态把题交上去了。但是它过了!竟然是暴力写错了...第三题的线段树优化暴力喜提70分,暴力真是个好算法。总结一下这次考试:对拍过了的爆零了,对拍过不了的AC了,看来以后还是静态查错比较靠谱。另:cena为什么总是把我的程序吃掉换成别人的啊...