这题!!我一看,嘿,线段树!好!写
然后就被教做人了
原来
这题要动态开点线段树
“原来 这题要动态开点线段树”
这句话听着,就好像我对动态开点线段树很熟悉的样子
其实我也是今天才搞清楚这是个啥
我的理解是,总的区间很大,但是查询次数很少的时候,就动态开
接下来的问题就是:怎么开
在递归的时候不用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.要考虑复杂度,这个很重要,不然写了也是白写