LNSYOJ203最大值【树状数组应用】【做题报告+树状数组深刻理解】
这道题是一个典型的树状数组查询有几个比某个数大/小的数的应用
题目描述
给定NN个区间,选定一个固定整数值TT,对于一个区间[ai,bi][ai,bi].
如果T<aiT<ai,那么T在这个区间的得分为X,
如果T>biT>bi,那么T在这个区间的得分为Z,
如果ai≤T≤biai≤T≤bi的话T在这个区间的得分为Y。
选定一个合适的T,使得T在所有区间的得分的和最大。
输入格式
第一行由空格隔开的4个整数N,X,Y,Z。 接下来N行每行由空格隔开的两个数ai,bi。
输出格式
一个整数表示最大的得分。
样例一
input
4 7 9 6 5 8 3 4 13 20 7 10
output
31
限制与约定
对于100%的数据,1≤N≤20000,0≤ai≤bi≤109.0≤X,Y,Z≤10001≤N≤20000,0≤ai≤bi≤109.0≤X,Y,Z≤1000
时间限制:1s1s
空间限制:256MB
首先@Unstoppable728感谢jdr大佬的讲解!!!
这道题第一眼看以为是二分,后来发现根本不用,就是一个应用
树状数组查询有几个比某个数大/小的数,
查询有几个比某个数小的数,很简单,我之前就会
1 void add(int pos,int val) 2 { 3 for(int i=pos;i<=tt;i+=lowbit(i)) 4 tree[i]+=val; 5 } 6 int ask(int pos,int op) 7 { 8 int ans=0; 9 for(int i=pos;i;i-=lowbit(i)) 10 ans+=tree[i]; 11 return ans; 12 }
只要在数组下标位置加一或减一,然后累加前缀和就OK;
然后最大值怎么破,然后我的脑洞大开,把数组全部颠倒过来存
1 add(n-i+1,1); 2 ask(n-i+1-1,1);
然后这道题是可以A的!
1 #include<cstdio> 2 #include<algorithm> 3 #define N 20011 4 #define lowbit(a) (a)&(-a) 5 using namespace std; 6 int n,x,y,z,tt=0; 7 int tree1[N*2],tree2[N*2]; 8 struct node{ 9 int val,op,hs; 10 }nd[N*2]; 11 bool cmp(node a,node b){return a.val<b.val;} 12 void add(int pos,int val,int op) 13 { 14 switch(op) 15 { 16 case 1: 17 for(int i=pos;i<=tt;i+=lowbit(i)) 18 tree1[i]+=val; 19 break; 20 case 2: 21 for(int i=pos;i<=tt;i+=lowbit(i)) 22 tree2[i]+=val; 23 break; 24 } 25 } 26 int ask(int pos,int op) 27 { 28 int ans=0; 29 switch(op) 30 { 31 case 1: 32 for(int i=pos;i;i-=lowbit(i)) 33 ans+=tree1[i]; 34 break; 35 case 2: 36 for(int i=pos;i;i-=lowbit(i)) 37 ans+=tree2[i]; 38 break; 39 } 40 return ans; 41 } 42 int main() 43 { 44 scanf("%d%d%d%d",&n,&x,&y,&z); 45 for(int i=1;i<=n;i++) 46 { 47 scanf("%d%d",&nd[i].val,&nd[i+n].val); 48 nd[i].op=0,nd[i+n].op=1; 49 } 50 sort(nd+1,nd+2*n+1,cmp); 51 for(int i=1;i<=2*n;i++) 52 { 53 if(nd[i].val==nd[i-1].val)nd[i].hs=tt; 54 else nd[i].hs=++tt; 55 } 56 for(int i=1;i<=2*n;i++) 57 { 58 if(nd[i].op==0)add(tt-nd[i].hs+1,1,1); 59 else add(nd[i].hs,1,2); 60 } 61 int ans=-1; 62 for(int i=1;i<=tt;i++) 63 { 64 int aa=ask(tt-i+1-1,1),bb=ask(i-1,2); 65 int kk=z*bb+aa*x+(n-aa-bb)*y; 66 ans=max(ans,kk); 67 } 68 printf("%d\n",ans); 69 return 0; 70 }
然后jdr巨佬回来了,进行了一番询问之后,
原来统计有几个比它大的只要反着更新,正着查询就OK
这是为什么呢??
其实我们只要验证树状数组的可逆性就好
来看这张图
进行脑补,会发现,其实树状数组和二进制有很大的关系,每一个1就是一个等级
树状数组为什么会成立?因为树状数组的查询和修改都是既不遗漏又不重复,而且会发现这个树状结构都是同构的,而且查询跳跃方式都相同
看这张图会发现比如说对于8(1000),他包含着后两位的所有组合表示的数,而其他的都不会包含8所包含的数,
也可以这样去理解,查询时在删去每个1时,都会把每个1下能表示的所有数进行累加
动手来模拟下查询和修改的过程,会发现它完全可以逆过来进行操作,它和数组的性质完全一样,只是将复杂度优化到了log级,这也就是为什么它叫树状数组
然后查有几个比某一个数大的就很好理解了,因为一个数它会影响比他小的数的比它大的数量,所以向前更新,查找是找它以后有几个数,即向后查询,也是完全OK的
感觉自己对树状数组有了更深的理解!!
本题AC代码
1 #include<cstdio> 2 #include<algorithm> 3 #define N 20011 4 #define lowbit(a) (a)&(-a) 5 using namespace std; 6 int n,x,y,z,tt=0; 7 int tree1[N*2],tree2[N*2]; 8 struct node{ 9 int val,op,hs; 10 }nd[N*2]; 11 bool cmp(node a,node b){return a.val<b.val;} 12 void add(int pos,int val,int op) 13 { 14 switch(op) 15 { 16 case 1: 17 for(int i=pos;i;i-=lowbit(i)) 18 tree1[i]+=val; 19 break; 20 case 2: 21 for(int i=pos;i<=tt;i+=lowbit(i)) 22 tree2[i]+=val; 23 break; 24 } 25 } 26 int ask(int pos,int op) 27 { 28 int ans=0; 29 switch(op) 30 { 31 case 1: 32 for(int i=pos;i<=tt;i+=lowbit(i)) 33 ans+=tree1[i]; 34 break; 35 case 2: 36 for(int i=pos;i;i-=lowbit(i)) 37 ans+=tree2[i]; 38 break; 39 } 40 return ans; 41 } 42 int main() 43 { 44 scanf("%d%d%d%d",&n,&x,&y,&z); 45 for(int i=1;i<=n;i++) 46 { 47 scanf("%d%d",&nd[i].val,&nd[i+n].val); 48 nd[i].op=0,nd[i+n].op=1; 49 } 50 sort(nd+1,nd+2*n+1,cmp); 51 for(int i=1;i<=2*n;i++) 52 { 53 if(nd[i].val==nd[i-1].val)nd[i].hs=tt; 54 else nd[i].hs=++tt; 55 } 56 for(int i=1;i<=2*n;i++) 57 { 58 if(nd[i].op==0)add(nd[i].hs,1,1); 59 else add(nd[i].hs,1,2); 60 } 61 int ans=-1; 62 for(int i=1;i<=tt;i++) 63 { 64 int aa=ask(i+1,1),bb=ask(i-1,2); 65 int kk=z*bb+aa*x+(n-aa-bb)*y; 66 ans=max(ans,kk); 67 } 68 printf("%d\n",ans); 69 return 0; 70 }