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 != '\0'){
            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 != '\0'){
            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] != '\0'){
        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] != '\0'){
        while(j != -1 && ( p[j+1] == '\0' || s[i] != p[j+1]))j = Next[j];
        if(s[i] == p[j+1])++j;
        f[i++] = j;
        if(p[j+1] == '\0'){
           //视情况
        }
    }
    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++;}
}

猜你喜欢

转载自www.cnblogs.com/smallocean/p/12681559.html