首页 > 技术文章 > 题解报告——Weed

genius777 原文

Weed

【题目描述】

duyege 的电脑上面已经长草了,经过辨认上面有金坷垃的痕迹。

为了查出真相,duyege 准备修好电脑之后再进行一次金坷垃的模拟实验。

电脑上面有若干层金坷垃,每次只能在上面撒上一层高度为 vi 的金坷垃,或者除掉最新 vi 层(不是量)撒的金坷垃(即撤回之前vi次撒的操作)。如果上面只留有不足 vi 层金坷垃,那么就相当于电脑上面没有金坷垃了。

duyege 非常严谨,一开始先给你 m 个上述操作要你依次完成。然后又对实验步骤进行了 q 次更改,每次更改都会改变其中一个操作为另外一个操作。每次修改之后都会询问最终金坷垃的量有多少,操作是叠加的。

【输入】

输入第一行为两个正整数 m、q,接下来 m 行每行 2 个整数 k、vi。k 为 0 时撒金坷垃,为 1 时除金坷垃。接下来 q 行每行 3 个整数 ci、k、vi,ci 代表被更改的操作是第 ci 个,后面 2 个数描述更改为这样的操作。

【输出】

输出 q 行代表每次金坷垃的量为多少。

 

【输入样例】

3 3 
0 10
1 1
0 11
1 1 10
2 0 20
3 1 1

【输出样例】

11
31 
0

【提示】

对于 30%的数据,m≤1000,q≤1000. 对于另外 20%的数据,每次 k=1 时都会将金坷垃清空。

对于 100%的数据,m≤2*10^5,q≤2*10^5,vi≤10^4。


 

【来源】

这是一道2016 noip的模拟题,前天被用来考楼下的同学,van dark爷 又说要我们做一做这3道模拟题,所以就来看看了,第一题还好,写个贪心一遍过,但是到了这道题,我就傻眼了,大脑直接掉线,除了暴力还能干蛤??!!

就在我无尽懵逼的时候,前排的那位大佬(当时楼下唯一做起了这道题的人)满脸轻松地对我说“这不是线段树的板子题吗”,当场傻了,这是线段树的板子???我是不是学了假的OI,我当然想过用线段树,但怎么维护嘛!维护每个操作,然后操作之间进行合并。对啊!!!我们可以维护操作,于是马上开始码代码。

码到一半,发现好像没有维护够东西,总感觉自己的合并有BUG,但又说不出为什么,这时前排大佬有满脸轻松地说了一句,维护一个对前面删除的变量,我当时没太搞明白,又不想,看别人博客,所以就没理前排大佬,继续按我的思路来,结果果然GG,调了一下午都没调出来。

回了家之后,越想越觉得自己的思路有问题,发现我还是需要记录一个删除变量,所以只有从头开始。最终,调了半个晚上,哇!!!我真的写出来了,内心一阵狂感动。。。

所以,停了好久没写博客的我,决定还是花点时间,记录下这道煞费苦心的题(太菜了)。

【思路分析】

这道题是线段树模板???虽然很感谢那位前排大佬,但是想到那张漫不经心的脸,和那句“这不是线段树的板子题吗””,内心就是一阵MMP,一度觉得自己是不是不适合学OI了,难道是我真的太菜了,不存在的吧。反而觉得,这道题是一道很不错的线段树(模板???)。

首先对于线段树的每个节点,我们维护的是这个区间的操作带来的影响,分别维护ci——>金克拉层数,vi——>金克拉的量,cut——>当前区间会对前面的区间造成多少的删除操作的影响。最后的根节点就是整个一系列操作带来的最终影响,这样修改起来十分迅捷,是logn的。

这道题最关键的就是update,即区间合并,我们需要好好考虑一下如何合并区间

因为对于每个节点右儿子都是后进行的操作,所以我们就需要优先考虑右儿子,再谈左儿子的事。

 

 1 int ask(int v,int GG)
 2 {
 3     int ls=t[v].son[0],rs=t[v].son[1];
 4     if(t[rs].ci==GG)//优先查找右儿子,如果右儿子刚好,那么但前区间的左儿子会全部都没被删 
 5     return t[v].vi-t[rs].vi;
 6     if(GG<t[rs].ci)
 7     return t[v].vi-t[rs].vi+ask(rs,GG);//如果右儿子剩多了,那么就到右儿子里面去查 
 8     else
 9     return ask(ls,GG-t[rs].ci+t[rs].cut);//如果右儿子不够就到左儿子里去,但我们就只需要查左儿子还差多少 
10 }
11 void update(int v)
12 {
13     int ls=t[v].son[0],rs=t[v].son[1];
14     if(t[rs].cut>=t[ls].ci)//右儿子把左儿子删完了 
15     {
16         t[v].cut=t[rs].cut+t[ls].cut-t[ls].ci;//那么当前区间的删除会有剩余 
17         t[v].ci=t[rs].ci;
18         t[v].vi=t[rs].vi;//因为左儿子被删完了 
19     }
20     else
21     if(t[rs].cut==0)//右儿子无删除操作 
22     {
23         t[v].cut=t[ls].cut; 
24         t[v].ci=t[ls].ci+t[rs].ci; 
25         t[v].vi=t[ls].vi+t[rs].vi;//右儿子不会影响当前区间
26     }
27     else//右儿子把左儿子删不完 
28     {
29         t[v].cut=t[ls].cut;//右儿子的删除用没了 
30         t[v].ci=t[rs].ci+t[ls].ci-t[rs].cut;//当前区间的层数会有剩余 
31         t[v].vi=t[rs].vi+ask(ls,t[rs].cut);//查询我们删除后左儿子剩下的金克拉量 
32     }
33 }

 

在代码实现环节之前,我还是要好好强调一下这道题的价值,至少对我来说,它让我明白了线段树能维护的东西很多,要敢于去想,去思考。不要拿着一颗线段树,整天就只知道区间最大值,区间和,区间乘(当然区间乘还是很扯淡的),也锻炼了我们线段树update的能力,挺有价值,你值得拥有!!!

【代码实现】

 1 #include<cstdio>
 2 #include<vector>
 3 #include<queue>
 4 #include<cstring>
 5 using namespace std;
 6 const int maxn=2e5+5;
 7 int root,cnt;
 8 struct sd{
 9     int ci,vi,cut,son[2],l,r;
10 }t[maxn*2];
11 int cc[maxn],vv[maxn];
12 int ask(int v,int GG)
13 {
14     int ls=t[v].son[0],rs=t[v].son[1];
15     if(t[rs].ci==GG)
16     return t[v].vi-t[rs].vi;
17     if(GG<t[rs].ci)
18     return t[v].vi-t[rs].vi+ask(rs,GG);
19     else
20     return ask(ls,GG-t[rs].ci+t[rs].cut);
21 }
22 void update(int v)
23 {
24     int ls=t[v].son[0],rs=t[v].son[1];
25     if(t[rs].cut>=t[ls].ci)
26     {
27         t[v].cut=t[rs].cut+t[ls].cut-t[ls].ci;
28         t[v].ci=t[rs].ci;
29         t[v].vi=t[rs].vi;
30     }
31     else
32     if(t[rs].cut==0) 
33     {
34         t[v].cut=t[ls].cut;
35         t[v].ci=t[ls].ci+t[rs].ci;
36         t[v].vi=t[ls].vi+t[rs].vi;
37     }
38     else
39     {
40         t[v].cut=t[ls].cut;
41         t[v].ci=t[rs].ci+t[ls].ci-t[rs].cut;
42         t[v].vi=t[rs].vi+ask(ls,t[rs].cut);
43     }
44 }
45 void build(int &v,int l,int r)
46 {
47     cnt++,v=cnt,t[v].l=l,t[v].r=r;
48     if(l==r)
49     {
50         if(cc[l]==0) t[v].ci=1,t[v].vi=vv[l],t[v].cut=0;
51         else t[v].vi=0,t[v].ci=0,t[v].cut=vv[l];
52         return;
53     }
54     int mid=(l+r)/2;
55     build(t[v].son[0],l,mid);
56     build(t[v].son[1],mid+1,r);
57     update(v);
58 }
59 void change(int v,int pos,int c1,int v1)
60 {
61     if(t[v].l==t[v].r&&t[v].l==pos)
62     {
63         if(c1==0) t[v].ci=1,t[v].vi=v1,t[v].cut=0;
64         else t[v].vi=0,t[v].ci=0,t[v].cut=v1;
65         return;
66     }
67     int mid=(t[v].r+t[v].l)/2;
68     if(pos<=mid) change(t[v].son[0],pos,c1,v1);
69     else change(t[v].son[1],pos,c1,v1);
70     update(v); 
71 }
72 int main()
73 {
74     int n,q,num=0;
75     scanf("%d%d",&n,&q);
76     for(int i=1;i<=n;i++)
77     scanf("%d%d",&cc[i],&vv[i]);
78     build(root,1,n);
79     for(int i=1;i<=q;i++)
80     {
81         int k,c1,v1;
82         scanf("%d%d%d",&k,&c1,&v1);
83         change(root,k,c1,v1);
84         printf("%d
",t[root].vi);
85     }
86     return 0;
87 }

 

 

 

推荐阅读