线段树测试-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 }
TET

 


 

  考试的时候,先写了前两题。这时候好像还很早,所以没有对拍,而是先去开第三题了。结果刚开始看第三题就开始头晕,所以做了很久很久也没想到怎么做,最后写了一个这么一个做法:对于每一行开线段树,每次暴力修改每一棵线段树的相应位置。这时已经没有多少时间了,所以就去写对拍。因为时间不够,对拍写的也很粗糙,第二题的暴力只能处理坐标为正数的情况,所以就只拍了这部分,过了就没管了。然而...负数部分是错的,把负数移至正数的最大值设的太大,所以数组就不够用了,数据又很强,就爆零了。然后写第一题的暴力,怎么拍怎么错,于是抱着凉凉的心态把题交上去了。但是它过了!竟然是暴力写错了...第三题的线段树优化暴力喜提70分,暴力真是个好算法。总结一下这次考试:对拍过了的爆零了,对拍过不了的AC了,看来以后还是静态查错比较靠谱。另:cena为什么总是把我的程序吃掉换成别人的啊...  

posted @ 2019-03-11 16:54  shzr  阅读(285)  评论(0编辑  收藏  举报