ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

《算法竞赛进阶指南》学习笔记【0X00基本算法】

2020-12-05 19:03:49  阅读:144  来源: 互联网

标签:std 进阶 int namespace 0X00 mid 算法 using include


断断续续看了2周,终于把第一章给结束了
接下来写写我自己的心得吧
感觉这本书还不错,因此打算长期学下去。
第一章的应用面很广,要好好打基础
接下来可能每干完一章都会写一下这样的学习笔记(flag)
不过每次学完都不是全懂,我只写下自己理解了的地方
等日后回头看可能有新的心得了再更新
接下来是正文

——————————————————————————————————————————————————————
1.位运算

1.1基本运算
与 &
或 |
非 ~
异或 ^
主要是有一些运算规律可以总结,假设x是某一个二进制位,即x=1 or x=0
那么有:

x&1==x,x&0==0;
x|1==1,x|0==x;
x^1==!x,x^0==x;
-x==~x+1;

1.2移位运算
有>>和<<两种,分别表示二进制下的数字右移和左移
利用这个可以遍历某个数字二进制的各位
应用一:快速幂(也是一个模板)
AcWing89https://www.acwing.com/problem/content/91/
直接上代码

#include <bits/stdc++.h>
using namespace std;
int power(int a,int b,int p){
    int ans=1%p;//防止p为1的特判
    for(;b;b>>=1){
        if(b&1)ans=(long long)ans*a%p;//开long long 防止爆int
        a=(long long)a*a%p;
    }
    return ans;
}
int main(){
    int a,b,p; cin>>a>>b>>p;
    cout<<power(a,b,p);
    return 0;
}

应用二:大整数乘法
AcWing90https://www.acwing.com/problem/content/92/
这里只列举书上的时间复杂度O(1)的做法,类似快速幂也可以的

#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
ull mul(ull a,ull b,ull p){
    a%=p; b%=p;
    ull c=(long double)a*b/p;//我们需要的是高的有效数字
    ull x=a*b,y=c*p;
    ull ans=x-y;
    if(ans<0)ans+=p;//x<y的特判
    return ans;
}
int main(){
    ull a,b,p;
    cin>>a>>b>>p;
    cout<<mul(a,b,p);
    return 0;
}

应用三:二进制状态压缩
讲真,我还没搞太明白,尤其是状态压缩DP那里(我好菜)
等后续我看懂了再来更新吧

2.递推与递归

2.1三个组合的枚举
1)AcWing 92https://www.acwing.com/problem/content/94/

#include <bits/stdc++.h>
using namespace std;
int n;
vector<int>v;
void solve(int x){
    if(x==n+1){
        for(int i=0;i<n;i++){
            cout<<v[i]<<" ";
        }
        cout<<endl;
        return;
    }
    v.push_back(x);//选择x
    solve(x+1);
    v.pop_back();//还原现场
    solve(x+1);
}
int main(){
    cin>>n;
    solve(1);
    return 0;
}

2)AcWing93https://www.acwing.com/problem/content/95/

#include <bits/stdc++.h>
using namespace std;
int n,m;
vector<int>v;
void solve(int x){
    if(v.size()>m || n+1-x+v.size()<m)return;//加一句特判就行
    if(x==n+1){
        for(int i=0;i<n;i++){
            cout<<v[i]<<" ";
        }
        cout<<endl;
        return;
    }
    v.push_back(x);//选择x
    solve(x+1);
    v.pop_back();//还原现场
    solve(x+1);
}
int main(){
    cin>>n>>m;
    solve(1);
    return 0;
}

3)AcWing94https://www.acwing.com/problem/content/96/
也可以用next_permutation

#include <bits/stdc++.h>
using namespace std;
int n;
int a[10];
bool b[10];
void solve(int x){//这里x的含义是遍历到第几个数字了
    if(x==n+1){
        for(int i=0;i<n;i++){
            cout<<a[i]<<" ";
        }
        cout<<endl;
        return;
    }
    for(int i=1;i<=n;i++){
        if(b[i])continue;
        a[x]=i;
        b[i]=true;//表示我要i这个数字
        solve(x+1);
        b[i]=false;//还原现场
    }
}
int main(){
    cin>>n;
    solve(1);
    return 0;
}

2.2汉诺塔问题
AcWing96https://www.acwing.com/problem/content/98/

#include<bits/stdc++.h>
using namespace std;
int main(){
    int d[15],f[15];
    d[1]=1;
    for(int i=2;i<=12;i++)d[i]=1+2*d[i-1];//这是只有三塔的汉诺塔
    memset(f,0x3f,sizeof(f));
    f[0]=0;
    for(int i=1;i<=12;i++){
        for(int j=0;j<i;j++){
            f[i]=min(f[i],2*f[j]+d[i-j]);
        }
    }
    for(int i=1;i<=12;i++)cout<<f[i]<<endl;
    return 0;
}

还有分治和分形,整不太明白,指书上的题目,后续再补吧

3.前缀和与差分

3.1前缀和
这个东西老好用了,自从学了这个感觉好多题目迎刃而解
看一道经典的二维前缀和
AcWing89https://www.acwing.com/problem/content/101/

#include<bits/stdc++.h>
using namespace std;
int a[5005][5005];
int main(){
    int n,r; cin>>n>>r;
    int p=r,q=r;
    for(int i=0,x,y,v;i<n;i++){
        cin>>x>>y>>v;
        x++, y++;//计算前缀和需要前导0
        p=max(p,x), q=max(q,y);//计算目标最远到哪里了
        a[x][y]+=v;
    }
    for(int i=1;i<=p;i++)
        for(int j=1;j<=q;j++)
            a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];//计算二维前缀和
    int ans=0;
    for(int i=r;i<=p;i++)
        for(int j=r;j<=q;j++)
            ans=max(ans,a[i][j]-a[i-r][j]-a[i][j-r]+a[i-r][j-r]);
    cout<<ans;
    return 0;
}

3.2差分
这个就是前缀和的逆运算
如果对于一个区间的操作可以使用差分来转化为对两个点的操作,继而提高效率
A从Wing100https://www.acwing.com/problem/content/102/
反正知道之后这题就是找规律的问题了

#include<bits/stdc++.h>
using namespace std;
int n;
int a[100010];
int b[100010];
int main(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
    b[1]=a[1];
    for(int i=2;i<=n;i++){
        b[i]=a[i]-a[i-1];
    }
    b[n+1]=0;
    long long sum1=0,sum2=0;
    for(int i=2;i<=n;i++){
        if(b[i]>0)sum1+=b[i];
        else if(b[i]<0)sum2+=abs(b[i]);
    }
    cout<<max(sum1,sum2)<<endl<<abs(sum1-sum2)+1;
    return 0;
}

4.二分

这个就没啥模板的了,如果对于一道题目,他的答案单调,并且判定难度小,找答案难度高,不如试试二分答案
除了有一些小技巧,这里我写下我自己学到的

//对于二分,有两种二分法
//1
int mid=(L+R)>>1;
if()L=mid+1;
else R=mid;
//2
int mid=(L+R+1)>>1;
if()L=mid;
else R=mid-1;

对于前者,因为取不到R,因此要L=mid+1,否则最后会忽略R这个答案
对于后者,因为取不到L,因此要R=mid-1,否则最后会忽略L这个答案

5.排序

5.1离散化
这也是很重要的一个思想,对于压缩空间和时间有一个重要应用,具体给一道题体会下
AcWing121https://www.acwing.com/problem/content/123/

#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
int sum[1010][1010];
int c,n;
vector<int>numbers;
vector<PII>points;
int get_point(int x){
    return lower_bound(numbers.begin(),numbers.end(),x)-numbers.begin();//返回x离散后的下标对应多少
}
bool check(int x){
    for(int x1=1,x2=1;x2<numbers.size();x2++){
        while(numbers[x2]-numbers[x1]+1>x)x1++;//找目前满足正方形的最小边长,下面那个也是
        for(int y1=1,y2=1;y2<numbers.size();y2++){
            while(numbers[y2]-numbers[y1]+1>x)y1++;
            if(sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1]>=c)return true;//找到了满足不小于c的情况
        }
    }
    return false;
}
int main(){
    cin>>c>>n;
    numbers.push_back(0);
    for(int i=0,x,y;i<n;i++){
        cin>>x>>y;
        numbers.push_back(x);
        numbers.push_back(y);
        points.push_back({x,y});
    }
    sort(numbers.begin(),numbers.end());//同时离散化x,y坐标后排序
    numbers.erase(unique(numbers.begin(),numbers.end()),numbers.end());//离散化(其实就是去除重复元素)
    for(int i=0;i<n;i++){
        int x=get_point(points[i].first),y=get_point(points[i].second);
        sum[x][y]++;//计算离散后的sum
    }
    for(int i=1;i<numbers.size();i++){
        for(int j=1;j<numbers.size();j++){
            sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
        }
    }
    int l=1,r=10000;
    while(l<r){
        int mid=l+r>>1;
        if(check(mid))r=mid;
        else l=mid+1;
    }
    cout<<l;
    return 0;
}

这题其实把离散化,前缀和,二分一起考了,主要是离散化的问题,为什么x,y坐标可以放在一起呢。
因为x的坐标离散化后在number里只有一个可以对应,这个有点绕,理解下就行。

5.2中位数
主要学一下一个对顶堆动态维护中位数的思想
其实就是在中位数前是大(小)顶堆,后是小(大)顶堆,然后模拟就行
AcWing106https://www.acwing.com/problem/content/108/

#include <bits/stdc++.h>
using namespace std;
vector<int>ans;
priority_queue<int>que1;//大顶堆
priority_queue<int,vector<int>,greater<int> >que2;//小顶堆
int main(){
    int p; cin>>p;
    while(p--){
        ans.clear();
        while(!que1.empty())que1.pop();
        while(!que2.empty())que2.pop();
        int cnt,n; cin>>cnt>>n;
        int mid;
        for(int i=1,x;i<=n;i++){
            cin>>x;
            if(i==1){
                ans.push_back(x);
                que2.push(x);
                mid=x;
                continue;
            }
            if(x<mid)que1.push(x);
            else que2.push(x);
            if(i%2==1){
                while(que2.size()-que1.size()!=1){
                    que2.push(que1.top());
                    que1.pop();
                }
                ans.push_back(que2.top());
            }else{
                while(que1.size()!=que2.size()){
                    if(que1.size()<que2.size()){
                        que1.push(que2.top());
                        que2.pop();
                    }else{
                        que2.push(que1.top());
                        que1.pop();
                    }
                }
            }
            mid=que2.top();
        }
        cout<<cnt<<" "<<ans.size()<<endl;
        for(int i=0;i<ans.size();i++){
            cout<<ans[i];
            if((i+1)%10==0 && i!=ans.size()-1)cout<<endl;
            if((i+1)%10!=0 && i!=ans.size()-1)cout<<" ";
        }
        cout<<endl;
    }
    return 0;
}

5.3第k大数
这个代码之后补上
5.4逆序对
主要给一个归并排序的模板,顺便计算下逆序对个数

#include<bits/stdc++.h>
using namespace std;
const int p=5e5+10;
int arr[p],b[p];
long long cnt=0;
void merge(int l,int mid,int r){
    int i=l,j=mid+1,k=0;
    while(i<=mid && j<=r){
        if(arr[i]<=arr[j])b[k++]=arr[i++];
        else b[k++]=arr[j++],cnt+=mid-i+1;
    }
    while(i<=mid)b[k++]=arr[i++];
    while(j<=r)b[k++]=arr[j++];
    for(i=l,j=0;i<=r;j++,i++)arr[i]=b[j];
}
void merge_sort(int l,int r){
    if(l==r)return;
    int mid=(l+r)/2;
    merge_sort(l,mid);
    merge_sort(mid+1,r);
    merge(l,mid,r);
}
int main(){
    int n;
    while(cin>>n && n){
        cnt=0;
        for(int i=0;i<n;i++)cin>>arr[i];
        merge_sort(0,n-1);
        cout<<cnt<<endl;
    }
    return 0;
}

6.倍增

目前我就只会一个RMQ模板

//RMQ模板/st表
#include<bits/stdc++.h>
using namespace std;
const int p=100010;
int f[p][19];
void st(){
      for(int j=1;j<=19;j++){
            for(int i=0;i+(1<<j)-1<n;i++){
		f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);
            }
      }
}
int Q(int l,int r){
      int len=log2(r-l+1);
      return max(f[i][len],f[r-(1<<len)+1][len]);
}
int main(){
      int n,m; //n个数,m次询问
      for(int i=0;i<n;i++)cin>>f[i][0];
      st();
      while(m--){
            int l,r; cin>>l>>r;
            l--; r--;
            cout<<Q(l,r)<<endl;
      }
      return 0;
}

7.贪心

这个很难总结知识点啊,直接看题目吧
如果是难的贪心还需要严谨的数学证明
这题是国王游戏问题,贪心的方法是按照左手乘右手大小贪心
哦对了,这题是贪心加上高精度(高精度乘除低精度)
AcWing114https://www.acwing.com/problem/content/116/

#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N=1010;
PII p[N];
vector<int> mul(vector<int> a,int b){//高精度*低精度
    vector<int> c;
    int t=0;
    for(int i=0;i<a.size();i++){
        t+=a[i]*b;
        c.push_back(t%10);
        t/=10;
    }
    while(t)c.push_back(t%10),t/=10;
    return c;
}
vector<int> div(vector<int> a,int b){//高精度/低精度
    vector<int> c;
    int is_first=false;
    for(int i=a.size()-1,t=0;i>=0;i--){
        t=t*10+a[i];
        int x=t/b;
        if(x || is_first){
            is_first=true;
            c.push_back(x);
        }
        t%=b;
    }
    return vector<int>(c.rbegin(),c.rend());
}
vector<int>manx(vector<int> a,vector<int> b){//高精度比大小(比字典序)
    if(a.size()>b.size())return a;
    if(a.size()<b.size())return b;
    if(vector<int>(a.rbegin(),a.rend())>vector<int>(b.rbegin(),b.rend()))return a;
    return b;
}
int main(){
    int n; cin>>n;
    for(int i=0,x,y;i<=n;i++){
        cin>>x>>y;
        p[i]={x*y,x};
    }
    sort(p+1,p+1+n);
    vector<int> product(1,1);
    vector<int> ans(1,0);
    for(int i=0;i<=n;i++){
        if(i)ans=manx(ans,div(product,p[i].first/p[i].second));
        product=mul(product,p[i].second);
    }
    for(int i=ans.size()-1;i>=0;i--)cout<<ans[i];
    cout<<endl;
    return 0;
}

或者这里有一题国王游戏的低配版
二分+贪心+前缀和
AcWing125https://www.acwing.com/problem/content/127/

#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int>PII;
PII p[50050];
int sum[50050];
int n;
bool check(int x){
    for(int i=1;i<n+1;i++){
        if(sum[i-1]-p[i].first+p[i].second>x)return false;
    }
    return true;
}
int main(){
    cin>>n;
    for(int i=1,w,s;i<=n;i++){
        cin>>w>>s;
        p[i]={w+s,w};
    }
    sort(p+1,p+n+1);
    for(int i=1;i<=n;i++)sum[i]=sum[i-1]+p[i].second;
    int l=-1e9,r=1e9;
    while(l<r){
        int mid=l+r>>1;
        if(check(mid))r=mid;
        else l=mid+1;
    }
    cout<<l;
    return 0;
}

大概就先这么多吧,如果遇到有启发的题目也尽量更新一下

标签:std,进阶,int,namespace,0X00,mid,算法,using,include
来源: https://www.cnblogs.com/JYF-AC/p/14090268.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有