zoukankan      html  css  js  c++  java
  • Acmer必须会手撕的代码

    在要求不能带板子和使用STL的情况下,只能手撕代码。

    而且手写的话说明对知识点了解更深,比赛的时候速度更快。(虽然我退役了)

    但是在笔试面试的时候很可能不能让你使用STL,这个时候就考验你对基本算法的理解(记忆)

    一般来说,出现在算法竞赛进阶指南的代码是必须会手撕的

    基本算法

    二分

    • 在单调递增序列(a)中查找$geq x $的数中最小的一个
      while(l<r){
          int mid = (l+r)>>1;/*右移运算 相当于除2并且向下取整*/
          if(a[mid]>=x) r=mid;
          else l=mid+1;
      }
      return a[l];
    
    • 在单调递增序列(a)中查找(leq x)的数中最大的一个
      while(l<r){
          int mid = (l+r+1)>>1;
          if(a[mid]<=x) l=mid;
          else r=mid-1;
      }
      return a[l];
    

    若是选满足答案最小的,初始化(l=L,r=R+1),如果答案是(R+1)那么就是没找到

    若是选满足答案最大的,初始化(l=L-1,r=R),如果答案是(L-1)那么就是没找到

    upper_bound()lower_bound()中,如果不存在的话确实是返回最后的下标+1,vector则是返回end()

    排序

    冒泡排序

    相邻比较,大的右移

    void bubble_sort() {
    	for (int i = n - 1; i > 0; --i) {
    		bool flag = false;
    		for (int j = 0; j + 1 <= i; ++j) 
    			if (a[j] > a[j + 1]) { 
    				std::swap(a[j], a[j + 1]);
    				flag = true;
    			}
    		if (!flag) return ; 
    	}
    	return ;
    }
    

    选择排序

    选择一个key,最大的右移

    void selection_sort() {
    	for (int i = 0; i < n; ++i) {
    		for (int j = i + 1; j < n; ++j) {
    			if(a[i] > a[j]) 
    				std::swap(a[i], a[j]);
    		}
    	}
    }
    

    插入排序

    在前面有序的情况下,选key插入到前面应该的位置

    int i,j,key;
    for(i = 2;i <= n;++i){
        key = a[i];
        j = i - 1;
        while(j > 0 && a[j] > key){
            a[j+1] = a[j];
            j--;
        }
        a[j] = key;
    }
    

    快速排序

    选中间的数,左边一定比它小,右边一定比它大,然后递归分治

    void quick_sort(int l, int r) {
    	if (l >=r ) return ;
    	int i = l - 1, j = r + 1, x = a[(l + r) >> 1];
    	while (i < j) {
    		do j--; while (a[j] > x);
    		do i++; while (a[i] < x);
    		if (i < j) std::swap(a[i], a[j]);
    		else quick_sort(l, j), quick_sort(j + 1, r);
    	}
    }
    

    归并排序

    int merge_sort(int l, int r) {
    	if (l >= r) return 0;
    	int mid = (l + r) >> 1, res = 0;
     
    	res += merge_sort(l, mid);
    	res += merge_sort(mid + 1, r);
     
    	int i = l, j = mid + 1, cnt = 0;
    	while (i <= mid && j <= r) 
    		if (a[i] <= a[j]) 
    			b[cnt++] = a[i++];
    		else {
    			res += mid - i + 1;
    			b[cnt++] = a[j++];
    		}
     
    	while (i <= mid) b[cnt++] = a[i++];
    	while (j <= r) b[cnt++] = a[j++];
     
    	for (int i = l, j = 0; j < cnt; ++i, ++j) a[i] = b[j];
    	return res;
    }
    

    堆排序

    大根堆的实现:x结点必须大于子结点

    struct heap{
        int *A;
        int length;
        heap(int n){
            length = n;
            A = new int[n+5];
            for(int i=1;i<=n;++i){
                cin>>A[i];
            }
        }
        void Max_Heapify(int pos){
            int l = pos*2;
            int r = pos*2+1;
            int largest;
            if(l <= length && A[l] > A[pos]){
                largest = l;
            }else{
                largest = pos;
            }
            if(r <= length && A[r] > A[largest]){
                largest = r;
            }
            if(largest != pos){
                swap(A[pos],A[largest]);
                Max_Heapify(largest);
            }
        }
        void build_max_heap(){
            for(int i=length/2;i>=1;--i){
                Max_Heapify(i);
            }
        }
        void heapSort(){
            build_max_heap();
            int n = length;
            for(int i=length;i>=2;--i){
                swap(A[1],A[i]);
                length--;
                Max_Heapify(1);
            }
            for(int i=1;i<=n;++i){
                cout<<A[i]<<" ";
            }
        }
    };
    

    哈希

    只要记住一个公式:

    [H(T) = H(S+T)-H(S)*p^{Length(T)} ]

    (H(x)):字符串(x)的哈希值

    (p):进制数

    (p^x)表示在(p)进制下左移(x)位的值

    代码中只要预处理 (f(x)):前缀哈希值 (p(x))(p^x)

    大数类

    大数面试官说要面向对象?可读性强?

    用三个栈维护大数加法(面试官说的可读性强的方法)

    #include<bits/stdc++.h>
    using namespace std;
    class bigNum{
    private:
        stack<int>num1,num2,sum;
    public:
        char *Num;
        bigNum(char *s){
            Num = s;
        }
        bigNum* add(bigNum* &right){
            for(int i=0;Num[i];++i){
                num1.push(Num[i] - '0');
            }
            for(int i=0;right->Num[i];++i){
                num2.push(right->Num[i] - '0');
            }
            int flag = 0,tmp,x,y;
            while(!num1.empty() || !num2.empty()){
                x = num1.empty() == 1 ? 0:num1.top();
                y = num2.empty() == 1 ? 0:num2.top();
                tmp = x + y + flag;
                flag = tmp/10;
                sum.push(tmp%10);
                if(!num1.empty()) num1.pop();
                if(!num2.empty()) num2.pop();
            }
            if(flag){
                sum.push(flag);
            }
            char *ans = new char[sum.size()+1];
            int pos = 0;
            while(!sum.empty()){
                ans[pos++] = sum.top() + '0';
                sum.pop();
            }
            return new bigNum(ans);
        }
        void printNum(){
            cout<<Num<<endl;
        }
    };
    char a[150],b[150];
    int main(){
        cin>>a;
        cin>>b;
        bigNum *left = new bigNum(a);
        bigNum *right = new bigNum(b);
        bigNum *sum = left ->add(right);
        sum ->printNum();
        return 0;
    }
    
    

    字符串

    字典树

    void ins(char *str){
        int len=strlen(str);
        int p=0;
        for(int i=0; i<len; i++){
            int ch=str[i]-'a';
            if(!tire[p][ch])
                tire[p][ch]=++ant;
            p=tire[p][ch];
        }
        cnt[p]++;
    }
    int srh(char *str){
        int ans=0;
        int len=strlen(str);
        int p=0;
        for(int i=0; i<len; i++){
            int ch=str[i]-'a';
            p=tire[p][ch];
            if(!p) return ans;
            ans+=cnt[p];
        }
        return ans;
    }
    

    其实也有指针写法,可能面试官只能看懂指针

    const int maxn = 26;
    struct tireNode{
        tireNode* next[maxn];
        bool endpos;
        tire(){
            endpos = 0;
        }
    };
    struct tire{
        tireNode *root;
        tire(){
            root = new tireNode();
        }
        void Insert(char *str){
            tireNode *now = root;
            while(*str != ''){
                int pos = *str - 'a';
                if(now->next[pos] == nullptr){
                    now -> next[pos] = new tireNode();
                }
                now = now -> next[pos];
                str++;
            }
            now -> endpos = 1;
        }
        bool Find(char *str){
            tireNode *now = root;
            while(*str != ''){
                int pos = *str - 'a';
                if(now -> next[pos] == nullptr){
                    return 0;
                }
                now = now -> next[pos];
                str++;
            }
            return now -> endpos;
        }
    };
    

    KMP

    (Next[i]):表示字符串([0...i])的前缀和后缀的最大匹配位置,前后缀都不能包括本身

    所以(Next[0]=-1),因为第一个字符肯定没有这个最大匹配位置

    (i)从1开始,(j)从-1开始

    根据循环不变式,在循环的过程中维持,$Next[i] (最后一定等于)j(,即)[0...j](一定是最长的以)i$为结尾的后缀与模式串前缀匹配的字符串。

    void get_Next(char *p){
        Next[0] = -1;
        int i = 1,j = -1;
        while(p[i] != ''){
            while(j != -1 && p[i] != p[j+1]) j = Next[j];
            if(p[i] == p[j+1])++j;
            Next[i++] = j;
        }
    }
    int kmp(char *s,char *p){
        int i = 0,j = -1;
        int ans = 0;
        while(s[i] != ''){
            while(j != -1 && ( p[j+1] == '' || s[i] != p[j+1]))j = Next[j];
            if(s[i] == p[j+1])++j;
            f[i++] = j;
            if(p[j+1] == ''){
               //视情况
            }
        }
        return ans;
    }
    

    AC自动机

    关键点就是从fafail序列里找到第一个(tr[u][i])不为空的,为了节省时间采用了路径压缩。

    namespace AC {
        int tr[N][26], tot;
        int e[N], fail[N];
    
        void insert(char *s) {
            int u = 0;
            for (int i = 1; s[i]; ++i) {
                if (!tr[u][s[i] - 'a']) tr[u][s[i] - 'a'] = ++tot;
                u = tr[u][s[i] - 'a'];
            }
            e[u]++;
        }
    
        queue<int> q;
    
        void build() {
            for (int i = 0; i < 26; ++i) {
                if (tr[0][i]) {
                    q.push(tr[0][i]);
                }
            }
            while (!q.empty()) {
                int u = q.front();
                q.pop();
                for (int i = 0; i < 26; ++i) {
                    if (tr[u][i]) {
                        fail[tr[u][i]] = tr[fail[u]][i];
                        q.push(tr[u][i]);
                    } else {
                        tr[u][i] = tr[fail[u]][i];
                    }
                }
            }
        }
    
        int query(char *t) {
            int u = 0, res = 0;
            for (int i = 1; t[i]; i++) {
                u = tr[u][t[i] - 'a'];  
                for (int j = u; j && e[j] != -1; j = fail[j]) {
                    res += e[j], e[j] = -1;
                }
            }
            return res;
        }
    }
    

    数学

    线性筛法

    采用性质:每个非素数都有一个最小的素因子

    void get_prime(){
        int pos = 0;
        for(int i=2;i<N;++i){
            if(!check[i]) {
                Prime[pos++]=i;
            }
            for(j=0;j < pos && i*Prime[j] < N;++j){
                check[i * Prime[j]] = true;
                if(i % Prime[j] == 0) {
                    break;
                }
            }
        }
    }
    

    约数

    倍数法得到正约数集合

    for(int i = 1;i <= n;++i){
        for(int j = 1;j <= n/i;++j){
            factor[i*j].push_back(i);
        }
    }
    

    单个数的约数:试除法

    for(int i = 1;i * i <= n;++i){
        if(n % i == 0){
            factor[++m] = i;
            if(i != n/i) factor[++m] = n/i;
        }
    }
    

    数据结构

    平衡树

    翻转至少要会,如果不是根节点,可以Rotate

    	inline void Rotate(int x) {
            int y = s[x].fa, z = s[y].fa, chk = get(x);
    
            //y与x的子节点相连
            s[y].ch[chk] = s[x].ch[chk ^ 1];
            s[s[x].ch[chk ^ 1]].fa = y;
    
            //x与y父子相连
            s[x].ch[chk ^ 1] = y;
            s[y].fa = x;
    
            // x与y的原来的父亲z相连
            s[x].fa = z;
            if(z) s[z].ch[y == s[z].ch[1]] = x;
    
            //只有x和y的sz变化了
            maintain(y);
            maintain(x);
        }
    

    树状数组

    int ask(int x){
        int ans = 0;
        for(;x;x -= x & -x) ans += c[x];
        return ans;
    }
    void add(int x,int y){
        for(;x <= N;x += x & -x) c[x] += y;
    }
    

    线段树

    struct node{
    	int l,r;
    	int sum,add;//和,延迟标记
    }t[maxn << 2];
    void build(int p,int l,int r){//后序遍历
    	t[p].l = l,t[p].r;
    	if(l == r) {t[p].sum = s[l];return;}
    	int mid = (l+r) >> 1;
    	build(p << 1,l,mid);
    	build(p << 1 |1,mid + 1,r);
    	t[p].sum = t[p << 1].sum + t[p << 1 | 1].sum;//更新来自子结点的答案
    }
    void spread(int p){
    	if(add(p)){//如果有标记
    		t[p << 1].sum = t[p].add * (t[p << 1].r - t[p << 1].l + 1);
    		t[p << 1 | 1].sum = t[p].add * (t[p << 1 | 1].r - t[p << 1 | 1].l + 1);
    		t[p << 1].add += t[p].add;
            t[p << 1 | 1].add += t[p].add;
            t[p].add;
    	}
    }
    void change(int p,int l,int r,int d){
        /*if(t[p].l == t[p].r){
        	t[p].sum += d;
        }如果不用延迟标记
        */
    	if(l <= t[p].l && r >= t[p].r){
    		t[p].sum += d * (t[p].r - t[p].l + 1);
            t[p].add += d;
            return;
    	}
    	spread(p);
    	int mid = t[p].l + t[p].r >> 1;
    	if(l <= mid) change(p << 1,l,r,d);
    	if(r > mid) change(p << 1 | 1,l,r,d);
        t[p].sum = t[p << 1].sum + t[p << 1 | 1].sum;//更新来自子结点的答案
    }
    long long ask(int p,int l,int r){
        if(l <= t[p].l && r >= t[p].r){
            return t[p].sum;
        }
        long long ans = 0;
        int mid = (t[p].l + t[p].r) >> 1;
        if(l <= mid) ans += ask(p << 1,l,r);
       	if(r > mid) ans += ask(p << 1|1,l,r);
        return ans;
    }
    

    图论

    最短路dij

    1. 初始化d[1]=0,其余结点为正无穷大
    2. 找出一个没有被标记,(d[x])最小的结点(x),标记结点(x)
    3. 扫描结点(x)的所有出边((x,y,z)),如果(d[y]>d[x]+z),则使用(d[x]+z)更新(y)
    memset(d,inf,sizeof(d));
    d[1] = 0;
    q.push(make_pair(0,1));
    while(!q.empty()){
     	int x = q.top().second;
        q.pop();
        if(v[x]) continue;
        v[x] = 1;
        for(int i=head[x];i;i=e[i].next){
            int y = e[i].to;
            int z = e[i].val;
            if(d[y] > d[x] + z){
                d[y] = d[x] + z;
                q.push(make_pair(-d[y],y));
            }
        }
    }
    

    最小生成树

    1. 建立并查集,每个点各自构成一个集合
    2. 把所有的边按权值从小到大排序,依次扫描每条边((x,y,z))
    3. (x,y)属于同一集合(连通),则忽略这条边
    4. 否则,合并(x,y)所在的集合,并把(z)累加到答案中
    int get(int x){
        if (x == fa[x]) return x;
        return fa[x] = get(fa[x]);
    }
    void solve(){
        for(int i=1;i<=n;++i) fa[i] = i;//1
        sort(e + 1,e + 1 + m,cmp);//2
        int ans = 0;
        for(int i = 1;i <= m;++i){
        	int x = get(e[i].x);
            int y = get(e[i].y);
            if(x != y){
                fa[x] = y;
                ans += e[i].z;
            }
        }
    }
    

    匈牙利算法

    bool dfs(int x){
        for(int i=head[x];i;i=e[i].next) {
            if(!vis[y = e[i].to]){
                vis[y] = 1;
                if(!match[y] || dfs(match[y])) {
                    match[y] = x;
                    return true;
                }
            }
        }
    }
    for(int i = 1;i <= n; ++i){
        memset(vis,0,sizeof(vis));
        if (dfs(i)) {ans++;}
    }
    
  • 相关阅读:
    Android——DEBUG 堆栈
    mycat读写分离与主从切换
    【測试工具】一个将Unix时间转换为通用时间的工具
    jQuery:多个AJAX/JSON请求相应单个回调
    iOS开发中经常使用的Xcode插件
    JAVA学习第二十六课(多线程(五))- 多线程间的通信问题
    我的mac OSX bash_profile文件
    angular学习(二)—— Data Binding
    python 读取grib grib2
    linux获取内存、cpu、负载、网口流量、磁盘信息
  • 原文地址:https://www.cnblogs.com/smallocean/p/12681559.html
Copyright © 2011-2022 走看看