[AcWing algorithm basic course] Chapter1 basic algorithm

Double pointer algorithm

We have already used the double pointer algorithm in quick sort and merge sort.

The first type: the first pointer points to sequence 1 and the second pointer points to sequence 2

The second type: two pointers point to the same sequence

for(int i=0,j=0;i<n;i++){
	while(j<i && check(i,j))
		j++;
	//The specific logic of each topic
}

Core idea: optimize the simple algorithm to O(n)

Question 1: there is a string like abc def hij. Please output each word (without spaces) and each word occupies one line.

#include <iostream>
#include <string>
using namespace std;

int main(){
    string str;
    getline(cin,str);
    //cout<<str<<endl;
    int n=str.size();
    for(int i=0;i<n;i++){
        int j=i;
        while(j<n && str[j]!=' ') j++;
        for(int k=i;k<j;k++) cout<<str[k];
        cout<<endl;
        i=j;
    }
    return 0;
}
Input:
abc def ghi
===============
Output:
abc
def
ghi

AcWing 799. Longest continuous unrepeated subsequence

We specify that pointer i is the end point and pointer j is the starting point. Enumerate the end point first and then the starting point.

//Violent practices O(n^2)
for(int i=0;i<n;i++){
	for(int j=0;j<=i;j++){
		if(check(j,i)){
			res=max(res,i-j+1);
		}
	}
}

The meaning of pointer j: where is the farthest left of j

Optimization ideas:

The idea of enumerating the end points i is the same as the naive algorithm. Let's see where the left end point of the interval with each i as the right end point is farthest from it.

We move the end point i one position at a time, and then find out where the new leftmost j can be.

When the end point moves backward, j will not go forward, which is monotonous. All can optimize simple practices.

//Double pointer algorithm: O(n)
for(int i=0;i<n;i++){
	//check to see if there are duplicate elements between J and I
	//As long as there is a repeating element between j and i, move j back one bit until there is no repeating element between j and i
	while(j<=i && check(j,i)) j++;
	res=max(res,i-j+1);
}
#include <iostream>
using namespace std;
const int N=100010;
int n;
int a[N];
//s stores the number of occurrences of each number in the current interval from j to i
int s[N];

int main(){
    scanf("%d",&n);
    for(int i=0;i<n;i++) scanf("%d",&a[i]);
    int res=0;
    for(int i=0,j=0;i<n;i++){
        //Add new number each time a[i]
        s[a[i]]++;
        //So if there are repeated numbers, it must be a[i] repeated
        while(s[a[i]]>1){
            s[a[j]]--;
            j++;
        }
        res=max(res,i-j+1);
    }
    cout<<res<<endl;
    return 0;
}

Bit operation

Application 1: find the k-th bit in the binary representation of n

n=15= ( 1111 ) 2 (1111)_2 (1111)2​

First shift the k to the last position n > > k

Let's take a look at the number of bits x&1

So the formula is: n > > k-1

Application 2: lowbit(x) returns the last bit 1 of X

x= ( 1010 ) 2 (1010)_2 (1010)2​

lowbit(x)= ( 10 ) 2 (10)_2 (10)2​

x= ( 101000 ) 2 (101000)_2 (101000)2​

lowbit(x)= ( 1000 ) 2 (1000)_2 (1000)2​

Formula: X & - x

-X is the complement, which is the inverse of the original code plus one, that is - x=~x+1

Common applications: subtract the last 1 every time and count the number of 1 in binary number x

AcWing 801. Number of 1 in binary

#include <iostream>
using namespace std;
int n;

int lowbit(int x){
    return x&-x;
}

int main(){
    scanf("%d",&n);
    while(n--){
        int x;
        scanf("%d",&x);
        int cnt=0;
        while(x!=0){
            x-=lowbit(x);//Subtract the last 1 of x each time
            cnt++;
        }
        cout<<cnt<<" ";
    }
    return 0;
}

Discretization

(this refers specifically to the ordered discretization of integers)

We have some values now, and their range is [0,10^9], but the number of these numbers is relatively small, and the number is between [0,10^5]

The characteristics of these values are: the range of values is large, but the number is small and sparse

In some cases, we may need to use these values as subscripts to solve the problem. It is very unrealistic to open an array without processing.

Therefore, we need to map this numerical sequence to natural numbers starting from 0 or 1


How to find out the value of a number x after discretization? Let's analyze it.

We use the subscript of this number as its discretized value, so the binary search can find the subscript of number x in all.

AcWing 802. Interval and

thinking

Put all the subscript in alls, sort it out, and then call the find function to map it to the natural number from 1.

Adding c to the integer x is: first find the value k of X after discretization, and then a[k]+=c on the discretized sequence

When finding the sum of all numbers between [l,r], first discretize L and r to the corresponding subscript position K l K_l Kl​ K r K_r Kr, please a again[ K l K_l Kl] to a[ K r K_r The sum of all numbers between Kr] is OK (use prefix and)

Usage of pair Summary of basic usage of C++ pair (sorting)
A big man gave this inscription a super detailed explanation AcWing 802. Interval and

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

//Store x and c and l and r with pair
typedef pair<int,int> PII;
const int N=300010;
int n,m;
//a[N] is the discretized array, and s[N] is the prefix and array
int a[N],s[N];
//All is a container for the number to be discretized
vector<int> alls;
//add stores x and C, and query stores l and r
vector<PII> add,query;

//The find function is used to discretize a number
int find(int x){
    int l=0,r=alls.size()-1;
    //Use dichotomy
    while(l<r){
        int mid=(l+r)>>1;
        if(alls[mid]>=x) r=mid;
        else l=mid+1;
    }
    return r+1;
}

int main(){
    scanf("%d%d",&n,&m);
    //Preparation operation: read data into and put them into corresponding containers respectively
    while(n--){
        int x,c;
        scanf("%d%d",&x,&c);
        add.push_back({x,c});
        alls.push_back(x);
    }
    while(m--){
        int l,r;
        scanf("%d%d",&l,&r);
        query.push_back({l,r});
        alls.push_back(l);
        alls.push_back(r);
    }
    //Sort and de duplicate all
    sort(alls.begin(),alls.end());
    alls.erase(unique(alls.begin(),alls.end()), alls.end());
    //At this time, although the data in the all container is de duplicated, it is still not discretized
    //The following is the add operation
    for(auto item:add){
        //Call the find function to discretize item.first, that is, x in the topic
        int t=find(item.first);
        a[t]+=item.second;
    }
    //The prefix sum [all. Size() is calculated because after discretization, the range is reduced and the number does not change]
    for(int i=1;i<=alls.size();i++) s[i]=s[i-1]+a[i];
    //The following is the query operation
    for(auto item:query){
        int l=find(item.first),r=find(item.second);
        cout<<s[r]-s[l-1]<<endl;
    }
    return 0;
}

Make your own wheels and implement a unique function

#include <iostream>
#include <vector>
using namespace std;

int unique(vector<int> &a){
    int j=0;
    for(int i=0;i<a.size();i++){
        if(i==0 || a[i]!=a[i-1]) a[j++]=a[i];
    }
    //a[0] to a[j-1] are the numbers that are not repeated in all a
    return j-1;
}

int main(){
    vector<int> a={1,2,2,2,3,3,3,3,3,4,4,5,6,6,7};
    cout<<unique(a)<<endl;
    return 0;
}
//The result is 6

Interval merging

There are many intervals. If two intervals intersect, they are merged into the same interval

Green is the merged interval

Idea:

First, sort all intervals according to the sorting rules of the left endpoint

Then maintain an interval A. there may be three relationships between interval b and interval a after interval a

Interval b cannot be in front of interval a, that is, the left end point of interval b must be less than or equal to the left end point of interval a, because we have sorted according to the sorting rules of the left end points.

When the case is 1 (inclusive), the interval maintained by us remains unchanged and no operation is required; In case 2 (with intersection), we extend interval A. in case 3 (without intersection), we can put interval a into the answer and update the interval to b

When interval b and interval a do not intersect, it means that interval b and all intervals behind interval b do not intersect with interval a, so we update the maintained interval to interval b

AcWing 803. Interval merging

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

typedef pair<int,int> PII;
int n;
vector<PII> segs;

bool cmp(PII a,PII b){
    if(a.first!=b.first) return a.first<b.first;
    else return a.second<b.second;
}

void merge(vector<PII> &segs){
    vector<PII> res;
    sort(segs.begin(),segs.end(),cmp);
    //We set the first maintenance interval from negative infinity to negative infinity
    int st=-2e9,ed=-2e9;
    for(auto seg:segs){
        if(ed<seg.first){
            if(st!=-2e9) res.push_back({st,ed});
            st=seg.first,ed=seg.second;
        }
        else ed=max(ed,seg.second);
    }
    //And add the last interval to the answer
    if(st!=-2e9) res.push_back({st,ed});
    segs=res;
}

int main(){
    scanf("%d",&n);
    while(n--){
        int l,r;
        scanf("%d%d",&l,&r);
        segs.push_back({l,r});
    }
    merge(segs);
    cout<<segs.size()<<endl;
    return 0;
}

Keywords: C++ Algorithm

Added by andrewb on Thu, 11 Nov 2021 23:08:41 +0200