Introduction notes of Niuke algorithm competition 5

Posted by son.of.the.morning on Thu, 10 Mar 2022 08:57:35 +0100

Finally, the line segment tree and tree array. Let's send a little bit first. Please look forward to more questions.

Briefly describe the segment tree:

What is a segment tree? In fact, it is an interval data structure. It looks like a tree, but each node maintains the attributes of a certain interval. The root node maintains the entire interval. Each node has two children and maintains the left and right half of the parent interval respectively. In this way, we can access the attributes of the interval through the splicing of intervals, The interval modification operation can also be completed in O (log n). However, if I find that the current interval is already a sub interval of the large interval I want to modify every time I modify the interval, we don't need to go down to modify each single point and directly modify the current parent interval, but because the child has not been modified at this time, We need to make a mark to ensure that the next time I inevitably need to access the value of a sub interval of this interval, I can correctly calculate its current value. This mark is called lazy mark.

Judgment of lazy mark:

1 - can subintervals be merged into large intervals

2 - can the overall modification of the interval be directly fed back to the value of the interval without the calculation of the sub interval

Balanced Lineup (nowcoder.com)

This is a four-star board question. I don't understand it.

Give a string of numbers and ask the difference between the maximum value and the minimum value of the interval

Train of thought: without train of thought, the line segment tree can be written directly, and the maximum and minimum values of the interval can be maintained each time

 #include <bits/stdc++.h>
 using namespace std;
 #define INF 0x3f3f3f3f
 const int maxn=100001;
 int val[maxn];
 int tr_ma[maxn<<2];
 int tr_mn[maxn<<2];
 void build(int p,int l,int r){
     if(l==r){
         tr_ma[p]=tr_mn[p]=val[l];
         return;
     }
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)>>1;
     build(ls,l,mid);
     build(rs,mid+1,r);
     tr_ma[p]=max(tr_ma[ls],tr_ma[rs]);
     tr_mn[p]=min(tr_mn[ls],tr_mn[rs]);
     return;
 }
 ​
 int ma(int p,int l,int r,int a,int b){
     if(a<=l&&r<=b){
         return tr_ma[p];
     }
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)>>1;
     int ans1=0,ans2=0;
     if(a<=mid){
         ans1=ma(ls,l,mid,a,b);
     }
     if(b>mid){
         ans2=ma(rs,mid+1,r,a,b);
     }
     return max(ans1,ans2);
 }
 ​
 int mn(int p,int l,int r,int a,int b){
     if(a<=l&&r<=b){
         return tr_mn[p];
     }
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)>>1;
     int ans1=INF,ans2=INF;
     if(a<=mid){
         ans1=mn(ls,l,mid,a,b);
     }
     if(b>mid){
         ans2=mn(rs,mid+1,r,a,b);
     }
     return min(ans1,ans2);
 }
 int main(){
     int n,q;
     cin>>n>>q;
     for(int i=1;i<=n;i++){
         cin>>val[i];
     }
     build(1,1,n);
     for(int i=0;i<q;i++){
         int a,b;
         cin>>a>>b;
         cout<<ma(1,1,n,a,b)-mn(1,1,n,a,b)<<endl;
     }
     return 0;
 }

A Simple Problem with Integers (nowcoder.com)

Board question

Question meaning: interval plus, ask interval sum

Idea: for the line segment tree board with lazy mark, the most important thing is to pass down the mark and modify the interval value according to the mark. Just figure out how to pass the father's mark to the son and modify the son's value at the same time

 #include <bits/stdc++.h>
 using namespace std;
 #define int long long
 const int maxn=100001;
 int x[maxn];
 int tree[maxn<<2],lazy[maxn<<2];
 void build(int p,int l,int r){
     if(l==r){
         tree[p]=x[l];
         return;
     }
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)/2;
     build(ls,l,mid);
     build(rs,mid+1,r);
     tree[p]=tree[ls]+tree[rs];
 }
 ​
 ​
 void push_down(int p,int l ,int r){
     if(l==r){
         return;
     }
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)/2;
     lazy[ls]+=lazy[p];
     lazy[rs]+=lazy[p];
     tree[ls]+=lazy[p]*(mid-l+1);
     tree[rs]+=lazy[p]*(r-mid);
     lazy[p]=0;
 }
 ​
 int calc(int p,int l,int r,int x,int y){
     if(x<=l&&r<=y){
         return tree[p];
     }
     if(lazy[p]!=0){
         push_down(p,l,r);
     }
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)/2;
     int ans1=0,ans2=0;
     if(x<=mid){
         ans1=calc(ls,l,mid,x,y);
     }
     if(y>mid){
         ans2=calc(rs,mid+1,r,x,y);
     }
     return ans1+ans2;
 }
 ​
 void add(int p,int l,int r,int x,int y,int val){
     if(x<=l&&r<=y){
         lazy[p]+=val;
         tree[p]+=(r-l+1)*val;
         return;
     }
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)/2;
     if(lazy[p]!=0){
         push_down(p,l,r);
     }
     if(x<=mid){
         add(ls,l,mid,x,y,val);
     }
     if(y>mid){
         add(rs,mid+1,r,x,y,val);
     }
     tree[p]=tree[ls]+tree[rs];
     return;
 }
 ​
 signed main(){
     int n,m;
     cin>>n>>m;
     for(int i=1;i<=n;i++){
         cin>>x[i];
     }
     build(1,1,n);
     for(int i=0;i<m;i++){
         char flag;
         cin>>flag;
         if(flag=='Q'){
             int a,b;
             cin>>a>>b;
             cout<<calc(1,1,n,a,b)<<endl;
         }else{
             int a,b,c;
             cin>>a>>b>>c;
             add(1,1,n,a,b,c);
         }
     }
     return 0;
 }

Data structure (nowcoder.com)

I find this problem very difficult, but there are only two stars...

Four operations: interval addition, interval multiplication, query interval sum, and query the sum of squares of interval elements

Idea: first of all, this question has two operations. If it is divided into two questions, it will be very simple, but when put together, there will be a problem: when this interval has both multiplication mark and addition mark, which operation should I do first? In fact, it's OK. We just need to manually specify a form to ensure that all the following maintenance can comply with this provision. For example, we stipulate that when we need to maintain addition and multiplication at the same time, we always ensure to multiply and then add first, that is, we always meet the form of kx+b. then when I have a new mark passed down, my two marks will become (k1x+b1)*k2+b2=k1k2x+b1k2+b2, That is, the multiplication mark is changed to k1k2 and the mark is changed to b1k2+b2. In this way, the mark download is completed. Here is how to modify the value of the interval according to the new multiplication and mark. What can I do? It's very simple. Let's make a formula and deduce it (len is the interval length)

It can be seen that we only need to know the original interval and the square sum of the original interval, and then we can calculate the new answer according to the two marks and bring it into the calculation

By the way bb, why isn't the question about qc sister(

 //The code is very long, but I can actually write the push_down, there are basically only boards left
 #include <bits/stdc++.h>
 using namespace std;
 #define int long long
 const int maxn=10005;
 int tr_sum[maxn<<2];
 int tr_plus[maxn<<2];
 int lazy_add[maxn<<2];
 int lazy_plus[maxn<<2];
 int x[maxn];
 void build(int p,int l,int r){
     if(l==r){
         tr_sum[p]=x[l];
         tr_plus[p]=x[l]*x[l];
         lazy_plus[p]=1;
         lazy_add[p]=0;
         return;
     }
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)/2;
     build(ls,l,mid);
     build(rs,mid+1,r);
     tr_sum[p]=tr_sum[ls]+tr_sum[rs];
     tr_plus[p]=tr_plus[ls]+tr_plus[rs]; 
     lazy_plus[p]=1;
     lazy_add[p]=0;
     return;
 }
 ​
 void push_down(int p,int l,int r){
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)/2;
     
     int k=lazy_plus[p];
     int val=lazy_add[p];
     
     tr_plus[ls]=k*k*tr_plus[ls]+2*k*val*tr_sum[ls]+(mid-l+1)*val*val;   
     tr_plus[rs]=k*k*tr_plus[rs]+2*k*val*tr_sum[rs]+(r-mid)*val*val;
 ​
     tr_sum[ls]*=k;
     tr_sum[rs]*=k;
     tr_sum[ls]+=(mid-l+1)*val;
     tr_sum[rs]+=(r-mid)*val;
     
     lazy_plus[ls]*=k;
     lazy_plus[rs]*=k;
     
     lazy_add[ls]*=k;
     lazy_add[rs]*=k;
     lazy_add[ls]+=val;
     lazy_add[rs]+=val;
     
     lazy_add[p]=0;
     lazy_plus[p]=1;
     return;
 } 
 ​
 int calc_sum(int p,int l,int r,int x,int y){
     if(x<=l&&r<=y){
         return tr_sum[p];
     }
     if(lazy_add[p]!=0||lazy_plus[p]!=1){
         push_down(p,l,r);
     } 
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)/2;
     int sum1=0,sum2=0;
     if(x<=mid){
         sum1=calc_sum(ls,l,mid,x,y);
     }
     if(y>mid){
         sum2=calc_sum(rs,mid+1,r,x,y);
     }
     return sum1+sum2;
 }
 ​
 int calc_plus(int p,int l,int r,int x,int y){
     if(x<=l&&r<=y){
         return tr_plus[p];
     }
     if(lazy_add[p]!=0||lazy_plus[p]!=1){
         push_down(p,l,r);
     } 
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)/2;
     int sum1=0,sum2=0;
     if(x<=mid){
         sum1=calc_plus(ls,l,mid,x,y);
     }
     if(y>mid){
         sum2=calc_plus(rs,mid+1,r,x,y);
     }
     return sum1+sum2;
 } 
 ​
 void pls(int p,int l,int r,int x,int y,int val){
     if(x<=l&&r<=y){
         
         lazy_plus[p]*=val;
         lazy_add[p]*=val;
         tr_sum[p]*=val;
         tr_plus[p]*=val*val;
         return;
     }
     if(lazy_add[p]!=0||lazy_plus[p]!=1){
         push_down(p,l,r);
     } 
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)/2;
     if(x<=mid){
         pls(ls,l,mid,x,y,val);
     }
     if(y>mid){
         pls(rs,mid+1,r,x,y,val);
     }
     tr_sum[p]=tr_sum[ls]+tr_sum[rs];
     tr_plus[p]=tr_plus[ls]+tr_plus[rs];
 }
 ​
 void add(int p,int l,int r,int x,int y,int val){
     if(x<=l&&r<=y){
         lazy_add[p]+=val;
         tr_plus[p]+=tr_sum[p]*2*val+val*val*(r-l+1);
         tr_sum[p]+=(r-l+1)*val;
         return;
     }
     if(lazy_add[p]!=0||lazy_plus[p]!=1){
         push_down(p,l,r);
     } 
     int ls=p<<1;
     int rs=ls+1;
     int mid=(l+r)/2;
     if(x<=mid){
         add(ls,l,mid,x,y,val);
     }
     if(y>mid){
         add(rs,mid+1,r,x,y,val);
     }
     tr_sum[p]=tr_sum[ls]+tr_sum[rs];
     tr_plus[p]=tr_plus[ls]+tr_plus[rs];
 }
 ​
 ​
 signed main(){
     int n,m;
     cin>>n>>m;
     for(int i=1;i<=n;i++){
         cin>>x[i];
     }
     build(1,1,n);
     for(int i=0;i<m;i++){
         int flag;
         cin>>flag;
         if(flag==1){
             int a,b;
             cin>>a>>b;
             cout<<calc_sum(1,1,n,a,b)<<endl;
         }else if(flag==2){
             int a,b;
             cin>>a>>b;
             cout<<calc_plus(1,1,n,a,b)<<endl;
         }else if(flag==3){
             int a,b,c;
             cin>>a>>b>>c;
             pls(1,1,n,a,b,c);
         }else{
             int a,b,c;
             cin>>a>>b>>c;
             add(1,1,n,a,b,c);
         }
     }
     return 0;
 }

​

Reverse order number (nowcoder.com)

Let's simply relax. The number of pairs in reverse order, three methods:

1 - violence

2-segment tree / tree array, maintaining the number of numbers in any interval before each number

3-merge sort statistics exchange times

After all, the line segment tree topic, so I wrote the line segment tree

 #include <bits/stdc++.h>
 using namespace std;
 const int maxn=100001;
 #define int long long / / long long wa1 is not enabled, woo woo
 int tree[maxn<<2];
 void add(int p,int l,int r,int pos){
     if(l==r){
         tree[p]++;
         return;
     }
     int ls=p<<1;
     int rs=p<<1|1;
     int mid=(l+r)>>1;
     if(pos<=mid){
         add(ls,l,mid,pos);
     }else{
         add(rs,mid+1,r,pos);
     }
     tree[p]=tree[ls]+tree[rs];
 }
 int query(int p,int l,int r,int x,int y){
     if(x<=l&&r<=y){
         return tree[p];
     }
     int ls=p<<1;
     int rs=p<<1|1;
     int mid=(l+r)>>1;
     if(y<=mid){
         return query(ls,l,mid,x,y);
     }else if(x>mid){
         return query(rs,mid+1,r,x,y);
     }else{
         return query(ls,l,mid,x,y)+query(rs,mid+1,r,x,y);
     }
 }
 signed main(){
     int n;
     cin>>n;
     int ans=0;
     for(int i=0;i<n;i++){
         int a;
         cin>>a;
         ans+=query(1,1,maxn,a+1,maxn);
         add(1,1,maxn,a);
     }
     cout<<ans<<endl;
     return 0;
 }

Atlantis (nowcoder.com)

Classical matrix covering problem

Give you N matrices and calculate the total area they cover

Idea: line segment tree + scan line + discretization. After writing for a day, we can only say nothing, because we are too * *. First, we need to have the idea of a scan line. This problem directly gives you all the rectangles and allows you to calculate statically. Therefore, we can observe from top to bottom line by line. As long as we can maintain it in real time for each Y, At present, the length of the interval covered by the rectangle in the x direction is enough. However, if we build the line segment tree directly on the x axis, the complexity is too high, and note that the range of n in the topic is only 100, so we can consider discretization, but I thought too much at first. I wanted to use two maps to store the mapping relationship between each other, After reading the problem solution, I found that people can maintain it by directly recording the endpoints on both sides in the tree node. The rest to note is the recursive judgment conditions and left and right boundaries

 #include <bits/stdc++.h>
 using namespace std;
 const int maxn=210;
 vector<double>xs;
 struct line{
     double x1,x2,y,flag;
 };
 line lines[201];
 bool com(line a,line b){
     return a.y>b.y;
 }
 ​
 struct node{
     //left,right: the actual left and right endpoints corresponding to each interval 
     //lazy: how many line segments are covered in this area
     //len: the length of the uncovered subinterval in this interval 
     double left,right,len;
     int lazy;
 };
 node tree[maxn<<2];
 ​
 void build(int p,int l,int r){
     tree[p].left=xs[l];
     tree[p].right=xs[r];
     tree[p].len=tree[p].lazy=0;
     if(r-l==1){
         return;
     }
     int ls=p<<1;
     int rs=p<<1|1;
     int mid=(l+r)>>1;
     build(ls,l,mid);
     build(rs,mid,r);
 }
 ​
 void change(int p,int l,int r){
     if(tree[p].lazy>0){
         tree[p].len=tree[p].right-tree[p].left;
     }else if(r-l==1){
         tree[p].len=0;
     }else{
         tree[p].len=tree[p<<1].len+tree[p<<1|1].len;
     }
 }
 ​
 void update(int p,int l,int r,double x1,double x2,int flag){
     if(tree[p].left==x1&&tree[p].right==x2){
         if(flag==0){
             tree[p].lazy++;
         }else{
             tree[p].lazy--;
         }
     }else{
         int ls=p<<1;
         int rs=p<<1|1;
         int mid=(l+r)>>1;
         if(tree[ls].right>=x2){
             update(ls,l,mid,x1,x2,flag);
         }else if(tree[rs].left<=x1){
             update(rs,mid,r,x1,x2,flag);
         }else{
             update(ls,l,mid,x1,tree[ls].right,flag);
             update(rs,mid,r,tree[rs].left,x2,flag);
         }
     }
     change(p,l,r);
     return;
 } 
 ​
 int main(){
     
     int n,ca=1;
     while(cin>>n&&n!=0){
         xs.clear();
         xs.push_back(-1);
         for(int i=1;i<=2*n;i+=2){
             cin>>lines[i].x1>>lines[i].y>>lines[i].x2;
             lines[i].flag=1;
             lines[i+1].x1=lines[i].x1;
             lines[i+1].x2=lines[i].x2;
             cin>>lines[i+1].y;
             lines[i+1].flag=0;
             xs.push_back(lines[i].x1);
             xs.push_back(lines[i].x2);
         }
         sort(xs.begin(),xs.end());
         sort(lines+1,lines+1+2*n,com);
         build(1,1,2*n);
         double h=lines[1].y;
         double ans=0;
         update(1,1,2*n,lines[1].x1,lines[1].x2,lines[1].flag);  
         for(int i=2;i<=n*2;i++){
             double mar=h-lines[i].y;
             h=lines[i].y;
             ans+=mar*tree[1].len;
             update(1,1,2*n,lines[i].x1,lines[i].x2,lines[i].flag);  
         }       
         cout<<"Test case #"<<ca++<<endl;
         cout<<"Total explored area: "<<ans<<endl;
     }
     
     return 0;
 }

Tree array:

It is smaller than the constant of line segment tree and easy to implement. It is easy to use when single point modification + interval query, or interval modification + single point query

Count Stars (nowcoder.com)

I was speechless. I took the line segment tree to write this question before I looked at the tree array. As a result, it timed out. I searched the problem solution online and found the tree array used all. Others said that this question card had the practice of line segment tree. I was stupid and put it later after learning the tree array. As a result, it was t again. Later, I found that it was because x and y might be equal to 0, so I had to save one bit later, Then I changed the practice of the line segment tree and added fast reading. It only takes 9ms. I mean, I'm speechless

Given n points, the level of each point is defined as the number of points at the lower left of the point (including positive left and positive lower). Try to count the number of points at each level.

Idea: since this question has two variables x and y, we try to eliminate the influence of one of the variables, that is, put it on the time axis, and this question also gives a hint: the stars are given according to the increasing order of Y coordinates, and the same y is given according to the increasing order of x coordinates. In this way, for each star, only the stars that may become the stars at the lower left of it have been read in front The abscissa is less than or equal to its star, so we can use the segment tree or tree array for single point modification, and then interval query.

 #include <bits/stdc++.h>
 using namespace std;
 #define int long long
 #define MAXN 32800
 ​
 void read(int &x){
     int f=1;x=0;char s=getchar();
     while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
     while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
     x*=f;
 }
 ​
 int tree[MAXN];
 inline int lowbit(int x){
     return x&(-x);
 } 
 inline void add(int x,int y,int n){
     for(int i=x;i<=n;i+=lowbit(i)){
         tree[i]+=y;
     }
 }
 ​
 inline int sum(int x){
     int ans=0;
     while(x>0){
         ans+=tree[x];
         x-=lowbit(x);       
     }
     return ans;
 }
 int ans[15001];
 signed main(){
     int n;
     read(n);
     int num=32768;
     for(int i=0;i<n;i++){
         int x,y;
         read(x);
         read(y); 
         ans[sum(x+1)]++;
         add(x+1,1,num); 
     }
     for(int i=0;i<n;i++){
         printf("%lld\n",ans[i]); 
     }
     return 0;
 }

Topics: C++ Algorithm data structure