codeforces 1111C creative snap 二分/线段树

这题!!我一看,嘿,线段树!好!写

然后就被教做人了

原来

这题要动态开点线段树

“原来 这题要动态开点线段树”

这句话听着,就好像我对动态开点线段树很熟悉的样子

其实我也是今天才搞清楚这是个啥

我的理解是,总的区间很大,但是查询次数很少的时候,就动态开

接下来的问题就是:怎么开

在递归的时候不用rt<<1|1和rt<<1这样的了,而是开两个数组,ls[]和rs[]

具体的我也不太懂,先看看这个模板吧,我本来以为这是写题解的那个博主自己想的,但是查了一下,别人的写法也是这样的

还有个地方,就是第16行,要注意!!

这个意思是,如果这个地方是空的,那么就要给它附上值,怎么说呢,我们在递归的时候有一个

ls[rt]和rs[rt],这些数组的初始值肯定是零,所以,每次发现它是零的时候,就要给他标个号,我决定叫它时间戳,和那个dfs序里的叫法一样

其实为什么要叫它时间戳呢?

我的意思是,它和dfs序一样,每当你进入一个节点的时候,你就会拥有一段子序列,也就是遍历子树时产生的顺序,进入这个节点里遇到的所有的子节点遍历时,他们是一段连续的序列(这是一个重要的性质),而动态开点的本质就是:根据查询再进行更新,以节省内存,所以就按照我们每次查询的顺序进行标号,也就是cnt++,所以必须初始化一个power[0]=a;

另外这个pushup函数里的操作也要注意,是取最小值

我们每次写线段树的时候,脑子里都要有一个图,那就是每个节点对应的线段是多长的,他代表的是哪个区间

比如:

---------------------------------------------------                1

------------------------   -------------------------       2                3

----------    -----------   ----------   ------------- 4       5       6         7

----  ----    -----  -----   ----  ----   ------  -----8 9 10 11 12 13 14 15

这题不需要查询函数,因为只需要一个power[1];

其实我感觉后面的那个二分做法和这个很像

常言道,一切搜索都能dp

这题我真的感觉有dp的精神在里面啊!!也许只是因为我第一次领略线段树区间更新的魅力吧

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define int long long
 4 #define lson l, m, ls[rt]
 5 #define rson m + 1, r, rs[rt]
 6 int n, k, a, b,pos,rt=1,cnt=1;
 7 const int maxn = 1e5+100;
 8 int tree[maxn << 5];
 9 int power[maxn << 5];
10 int rs[maxn << 5], ls[maxn << 5];
11 void pushup(int rt,int len){
12     tree[rt] = tree[ls[rt]] + tree[rs[rt]];
13     power[rt] = min(b * tree[rt] * len, power[ls[rt]] + power[rs[rt]]);
14 }
15 void update(int p,int l,int r,int &rt){
16     if(!rt)
17         rt = ++cnt;
18     if(l==r){
19         tree[rt]++;
20         power[rt] = b * tree[rt];
21         return;
22     }
23     int m = (l + r) >> 1;
24     if(p<=m)
25         update(p, lson);
26     else
27         update(p, rson);
28     pushup(rt,r-l+1);
29 }
30 int32_t main(){
31     cin >> n >> k >> a >> b;
32     power[0] = a;
33     for (int i = 0; i < k;i++){
34         cin >> pos;
35         update(pos, 1, 1 << n, rt);
36     }
37     cout << power[1];
38     //system("pause");
39     return 0;
40 }

再上二分的做法,

注意,这个地方,输入的那一行人所在的位置要排序!!

我认为有两个原因,

1.因为upper_bound()函数和lower_bound()函数是基于二分查找的,所以必须要先排序

2.这样更新的时候不会漏??

不对,第二个原因不成立,线段树每次更新的时候是同步的,应该不是这个原因

好吧,原因应该只有一个

注意用vector,这个我感觉比较好看!!!

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define int long long
 4 vector<int>v;    
 5 int n, k, a, b;
 6 int dfs(int l,int r){
 7     int len = upper_bound(v.begin(), v.end(), r) - lower_bound(v.begin(), v.end(), l);
 8     if(len==0)
 9         return a;
10     int power = len * b * (r - l + 1);
11     if(l==r)//l==r,则r-l+1==1,这代表长度为1,并且有人在里面的点
12         return power;
13     int m = (l + r) >> 1;
14     return min(power, dfs(l, m) + dfs(m + 1, r));
15 }
16 int32_t main(){
17     cin >> n >> k >> a >> b;
18     for (int i = 0; i < k;i++){
19         int x;
20         cin >> x;
21         v.push_back(x);
22     }
23     sort(v.begin(), v.end());//
24     cout << dfs(1, 1 << n);
25     //system("pause");
26     return 0;
27 }

最后的反思:

1.自己之前用线段树没写出来,线段树掌握得还不够熟练!而且还有很多好的做法还没学到!

(要不怎么说教做人呢)而且自己对线段树的理解需要加深,不仅是掌握

2.在考虑线段树之前,应该先想到二分/搜索/dp这样的,不要一开始就拿线段树套

3.要考虑复杂度,这个很重要,不然写了也是白写

猜你喜欢

转载自www.cnblogs.com/guaguastandup/p/10445967.html
今日推荐