模版总结小全

目录

基础算法

位运算

常见的位运算符

lowbit运算

取二进制第K位的值

位运算链接

排序

快速排序

归并排序(逆序对)

高精度

高精度加法

高精度减法

高精乘低精

 高精除低精

前缀和&&差分

一维前缀和

二维前缀和

一维差分

二维差分

KMP算法

快速幂

龟速乘

BFS

最短步数问题

洪水填充:连通块问题

最短路:输出路径

DFS

组合型枚举:无限制数量

组合型枚举:有数量限制

全排列

八皇后

图论:

        存储与遍历

               vectoer

临接矩阵-数组

临接表-链表

拓扑排序

最短路径算法

dijkstra算法(无负权,单源最短路,O(nm))

优化版dijkstra算法(一般O(n),最坏O(n,m))

Bellman_Ford算法

SPFA算法

Floyd算法

常见错误

并查集

查询合并操作

动态规划(DP)

背包DP

01背包-二维版

01背包-一维优化

完全背包-朴素二维

完全背包-二维版

 完全背包-一维优化

多重背包-朴素版

多重背包-二进制优化版

分组背包-二维版

分组背包-一维优化

线性DP

数字三角形(数字金子塔)

 最长上升子序列

最长公共子序列

区间DP

合并石子


基础算法

位运算

这个y总的视频也没有详细介绍。(一般不会单独出一道题,常用作优化)

常见的位运算符

lowbit运算

#include<bits/stdc++.h>
using namespace std;

int lowbit(int x){
	//lowbit算法是去二进制从后向前的一个1的位置那个位置到最后的值  
	//如1001001 -> 1 -> 100 -> 100 
    return x & -x; 
}

int main(){
    int t; cin >> t;
    while(t--){
        int n,cnt = 0; cin >> n;
        while(n){
            n -= lowbit(n);
            cnt++;
        }
        cout << cnt << " ";
    }
    return 0;
}

取二进制第K位的值

a >> k & 1

位运算链接

https://www.cnblogs.com/zengzi233/p/12117098.html

c++之位运算(详解,初学者绝对能看懂)_c++位运算-CSDN博客

排序

其实这两个排序都是二分了两边进行排序所以速度不算慢。

快速排序

#include<bits/stdc++.h>
using namespace std;

const int N = 1e6;
int n,q[N];

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

int main(){
    scanf("%d",&n);
    for(int i = 0; i < n; i++) scanf("%d",&q[i]);
    quick_sort(q,0,n-1);
    for(int i = n-1; i >= 0 ; i--) printf("%d ",q[i]);
    return 0;
}

归并排序(逆序对)

#include<bits/stdc++.h>
using namespace std;

const int N = 1e6+10;

int n,q[N],tmp[N];
long long res;

void merge_sort(int q[],int l, int r){
    if(l >= r) return;

    int mid = l+r >> 1;
    merge_sort(q,l,mid);
    merge_sort(q,mid+1,r);

    int k = 0,i = l,j = mid+1;
    while(i <= mid && j <= r){
        if(q[i] <= q[j]) tmp[k++] = q[i++];
        else{
            tmp[k++] = q[j++];
            res += mid - i + 1; //求逆序对,去掉这行就是归并排序
        }
    }
    while(i <= mid) tmp[k++] = q[i++];
    while(j <= r) tmp[k++] = q[j++];
    for(int i = l, j = 0; i <= r; i++,j++) q[i] = tmp[j];
}

int main(){
    scanf("%d",&n);
    for(int i = 0; i < n; i++) scanf("%d",&q[i]);
    merge_sort(q,0,n-1);
    cout << res << endl;
    return 0;
}

高精度

高精度算法的本质是用字符串模拟运算,从而达到计算的需求。但是这种算法在CSP的比赛里却不经常用(毕竟谁会单纯考你个这)模板也简单好用。

高精度加法

#include<bits/stdc++.h>
using namespace std;

const int N = 1e6+10;

vector<int> add(vector<int> & A,vector<int> &B){
    if(A.size() < B.size()) return add(B,A);
    vector<int> C;
    int t = 0;
    for(int i = 0; i < A.size(); i++){
    	//进行加法,统计单位之和 
        t += A[i]; 
        if(i < B.size()) t += B[i];
        //存储当位的数 
        C.push_back(t%10);
        //进位 
        t /= 10;
    }
    //看最后一位如果还有在存储进去,就比如9+9,两个一位数相加会得出一个2位数 
    if(t) C.push_back(t); 
    return C;
}

int main(){
    string a,b; //用字符串读入两个数 
    vector<int> A,B;
    cin >> a >> b;
    //vector倒置存储,方便模拟加法进位操作 
    for(int i  = a.size()-1; i >= 0; i--) A.push_back(a[i]-'0');  
    for(int i = b.size()-1; i >= 0; i--) B.push_back(b[i]-'0');
    auto C = add(A,B); 
    for(int i = C.size()-1; i >= 0; i--) cout << C[i]; 
    return 0;
}

高精度减法

#include<bits/stdc++.h>
using namespace std;

//判断两个数的大小,方便进行绝对值相减,判断符号 
bool cmp(vector<int> &A,vector<int> &B){
    if(A.size() != B.size()) return A.size() > B.size();
    else {
        for(int i = A.size()-1; i >= 0; i--){
            if(A[i] != B[i]) return A[i] > B[i];
        }
        return true; //两个数相等
    }
}

vector<int> sub(vector<int> &A, vector<int> &B){
    vector<int> C;
    for(int i = 0,t = 0; i < A.size(); i++){
        t = A[i]- t; //借位 
        //相减 
        if(i < B.size()) t -= B[i];
        C.push_back((t+10) % 10);
        if(t < 0) t = 1; //借位 
        else t = 0;
    }
    while(C.size() > 1 && C.back() == 0) C.pop_back(); //去前导零 
    return C;
} 

int main(){
    string a,b; 
    vector<int> A,B; 
    cin >> a >> b;
    //导致存储 
    for(int i = a.size()-1; i >= 0; i--) A.push_back(a[i]-'0');
    for(int i = b.size()-1; i >= 0; i--) B.push_back(b[i]-'0');
    auto C = sub(A,B);
    if(cmp(A,B)){
        auto C = sub(A,B);
        for(int i = C.size()-1; i >= 0; i--) cout << C[i];
    }
    else{
        auto C = sub(B,A);
        cout << '-';
        for(int i = C.size()-1; i >= 0; i--) cout << C[i];
    }

    return 0;
}

高精乘低精

#include<bits/stdc++.h>
using namespace std;

vector<int> mul(vector<int> &A,int B){
    int t = 0;
    vector<int> C;
    for(int i = 0; i < A.size() || t; i++){
        if(i < A.size()) t += A[i]*B;
        C.push_back(t%10);
        //进位 
        t /= 10;
    }
    int i = C.size()-1;
    while(C.size() > 1 && C.back() == 0) C.pop_back(); //去前导零 
    return C;
}

int main(){
    string a; 
    int b; 
    //分别读入高精,低精 
    cin >> a >> b;
    vector<int> A;
    for(int i = a.size()-1; i >= 0; i--) A.push_back(a[i]-'0');
    auto C = mul(A,b);
    for(int i = C.size()-1; i >= 0; i--) cout << C[i];
    return 0;
}

 高精除低精

#include<bits/stdc++.h>
using namespace std;

vector<int> div(vector<int> A,int b,int &r){
    vector<int> C;
    r = 0;
    for(int i = A.size()-1; i >= 0; i--){
        r = r*10+A[i];
        C.push_back(r/b);
        //r为余数 
        r %= b;
    }
    reverse(C.begin(),C.end()); //翻转,因为除法其实是从高位到低位比较好做 
    while(C.size() > 1 && C.back() == 0) C.pop_back(); //去前导零 
    return C;
}

int main(){
    string a;
    int b;
    vector<int> A;
    cin >> a >> b;
    for(int i = a.size()-1; i >= 0; i--) A.push_back(a[i]-'0');
    int r;
    auto C = div(A,b,r);
    for(int i = C.size()-1; i >= 0; i--) cout << C[i];
    cout << endl << r << endl;
    return 0;
}

前缀和&&差分

一维前缀和

#include<bits/stdc++.h>
using namespace std;

const int N = 1e6+10;
int a[N],s[N];
int n,m,l,r; //n个数,m次询问

int main(){
    scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++){
	    scanf("%d", &a[i]);
	    s[i] += s[i-1]+a[i];
	}
	for(int i = 1; i <= m; i++){
	    scanf("%d%d", &l, &r);
	    printf("%d\n",s[r]-s[l-1]);
	}
	return 0;
}

二维前缀和

#include<bits/stdc++.h>
using namespace std;

int a[1010][1010],sum[1010][1010];

int main(){
	int m,n,q,x1,x2,y1,y2;
	cin >> n >> m >> q;
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= m; j++) cin >> a[i][j];
	}
	sum[1][1] = a[1][1];
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= m; j++) sum[i][j] = sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
	}
	for(int i = 1; i <= q; i++){
		cin >> x1 >> y1 >> x2 >> y2;
		cout << sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1] << endl;
	}
	return 0;
}

一维差分

#include<bits/stdc++.h>
using namespace std;

int a[N],c[N],s[N];

int main(){
	int n,m;
	cin >> n >> m;
	for(int i = 1; i <= n; i++) cin >> a[i];
	for(int i = 1; i <= n; i++) c[i] = a[i]-a[i-1];
	for(int i = 1; i <= m; i++){
		int l,r,p;
		cin >> l >> r >> p;
        //差分操作
		c[l] += p;
		c[r+1] -= p; 
	}
	for(int i = 1; i <= n; i++) s[i] = s[i-1] + c[i]; //还原出原数组 
	for(int i = 1; i <= n; i++) cout << s[i] << " "; 
	cout << endl;
	return 0;
}

二维差分

#include<bits/stdc++.h>
using namespace std;

const int N = 1e3+10;
int c[N][N],ans[N][N];

int main(){
	int n,m,q;
	cin >> n >> m >> q;
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= m; j++){
			int x; cin >> x;
			c[i][j] += x;
			c[i+1][j] -= x;
			c[i][j+1] -= x;
			c[i+1][j+1] += x;
		}
	}
	for(int i = 1; i <= q; i++){
		int x1,x2,y1,y2,cc;
		cin >> x1 >> y1 >> x2 >> y2 >> cc;
		c[x1][y1] += cc;
		c[x2+1][y1] -= cc;
		c[x1][y2+1] -= cc;
		c[x2+1][y2+1] += cc;
	}
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= m; j++){
			ans[i][j] = ans[i-1][j]+ans[i][j-1]-ans[i-1][j-1]+c[i][j];
			cout << ans[i][j] << " ";
		}
		cout << endl;
	}
	return 0;
}

KMP算法

字符串匹配算法

#include<bits/stdc++.h>
using namespace std;

const int N = 1e6+10;
int n,m; 
char p[N],s[N]; //p为模式串,s是文本串 
int ne[N];
int res = 0;

int main(){
	cin >> s+1 >> p+1;
	n = strlen(p+1),m = strlen(s+1);
	//求next,就是以next[i]结尾的后缀能够匹配的从一开始的最大前缀的长度 (求模式串的next数组)
	for(int i = 2,j = 0; i <= n; i++){
		while(j && p[i] != p[j+1]) j = ne[j];
		if(p[i] == p[j+1]) j++;
		ne[i] = j;
	} 
	//kmp匹配过程 
	for(int i = 1,j = 0; i <= m; i++){
		while(j && s[i] != p[j+1]) j = ne[j];
		if(s[i] == p[j+1]) j++;
		if(j == n){
			res++;
			cout << i-n+1 << " "; //每次出现的位置,模式串开头的位置下标 
			j = ne[j];
		}
	}
	cout << endl;
	cout << res << endl; //res就是出现次数
	return 0;
}

快速幂

a^b%mod

#include<bits/stdc++.h>
using namespace std;

int qmi(int a,int k,int p){
	int res = 1;
	while(k){
		if(k & 1) res = res*a%p;
		a = a*a%p;
		k >>= 1;
	}
	return res;
}

int main(){
	int a,b,c;
	cin >> a >> b >> c;
	cout << qmi(a,b,c) << endl;
	return 0;
}

龟速乘

a*b%mod

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;

LL qadd(LL a,LL b,LL p){
	LL res = 0;
	while(b){
		if(b & 1) res = (res+a)%p;
		a = (a+a)%p;
		b >>= 1;
	}
	return res;
}

int main(){
	LL a,b,p;
	scanf("%lld%lld%lld",&a,&b,&p);
	printf("%lld\n",qadd(a,b,p));
	return 0;
} 

数据结构

链表

单链表

const int N = 1e6+10;
//h头结点下标,e[i]表示节点的值,ne[i]表示下一个指针,idx表示存储到哪个数了
int h = -1,ne[N],e[N],idx = 0;

//头插法
void add_head(int x){
    e[idx] = x;
    ne[idx] = h;
    h = idx;
    idx++;
}

//插入到k后面
void add_k(int k,int x){
    e[idx] = x;
    ne[idx] = ne[k];
    ne[k] = idx;
    idx++;
}

//删除下标k后面的点
void remove(int k){
    ne[k] = ne[ne[k]];
}

双链表

const int N = 100010;

int m;
int e[N], l[N], r[N], idx;
r[0] = 1, l[1] = 0,idx = 2;

// 在节点a的右边插入一个数x
void insert_r(int a, int x)
{
    e[idx] = x;
    l[idx] = a, r[idx] = r[a];
    l[r[a]] = idx, r[a] = idx ++ ;
}

//在a的左边插入x 
void insert_l(int a,int x){
    insert_r(l[a],x);
}

// 删除节点a
void remove(int a)
{
    l[r[a]] = l[a];
    r[l[a]] = r[a];
}

BFS

最短步数问题

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;

const int N = 50;
char g[N][N],d[N][N];
int dx[] = {-1,0,1,0};
int dy[] = {0,1,0,-1};
int n,m;

int bfs(int x,int y){
	queue<pair<int,int> > q;
	q.push({x,y});
	memset(d,-1,sizeof(d));
	d[x][y] = 1;
	while(!q.empty()){
		auto t = q.front();
		q.pop(); 
		for(int i = 0; i < 4; i++){
			int sx = t.first+dx[i];
			int sy = t.second+dy[i];
			if(sx >= 0 && sy >= 0 && sx < n && sy < m && d[sx][sy] == -1 && g[sx][sy] == '.'){
				q.push({sx,sy});
				d[sx][sy] = d[t.first][t.second]+1;
			}
		} 
	}
	return d[n-1][m-1];
}

int main(){
	cin >> n >> m;
	for(int i = 0; i < n; i++) cin >> g[i];
	cout << bfs(0,0) << endl;
	return 0;
} 

洪水填充:连通块问题

#include<iostream>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;

const int N = 120;
char g[N][N];
bool d[N][N];
int n,m;
int dx[8] = {-1,-1,-1,0,1,1,1,0};
int dy[8] = {-1,0,1,1,1,0,-1,-1};

int bfs(int sx,int sy){
	queue<pair<int,int>> q;
	q.push({sx,sy});
	g[sx][sy] = '.';
	while(!q.empty()){
		auto h = q.front();
		q.pop();
		for(int i = 0; i < 8; i++){
			int x = h.first + dx[i];
			int y = h.second + dy[i];
			if(x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 'W'){
				q.push({x,y});
				g[x][y] = '.';
			} 
		}
	}
}

int main(){
	cin >> n >> m;
	int ans = 0;
	for(int i = 0; i < n; i++) cin >> g[i];
	for(int i = 0; i < n; i++){
		for(int j = 0; j < m; j++){
			if(g[i][j] == 'W'){
				bfs(i,j);
				ans++;
			}
		}
	}
	cout << ans << endl;
	return 0;
}

最短路:输出路径

#include<iostream>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;

const int N = 50;
int a[5][5];
bool st[N][N];

typedef pair<int,int> PII; 

PII h[25];
PII Prev[5][5];
int hh,tt;

int dx[4] = {-1,0,1,0};
int dy[4] = {0,1,0,-1};

int bfs(int sx,int sy){
	hh = 0;
	tt = -1;
	h[++tt] = {sx,sy};
	memset(Prev,-1,sizeof Prev);
	while(hh <= tt){
		PII t = h[hh++];
		for(int i = 0; i < 4; i++){
			int x = t.first + dx[i];
			int y = t.second + dy[i];
			if(x >= 0 && x < 5 && y >= 0 && y < 5 && a[x][y] == 0 && st[x][y] == false){
				h[++tt] = {x,y};
				Prev[x][y] = t;
				st[x][y] = 1;
			}
		}
	}
} 


int main(){
	for(int i = 0; i < 5; i++){
		for(int j = 0; j < 5; j++){
			cin >> a[i][j];
		}
	}
	bfs(4,4);
	PII end(0,0);
	while(true){
		cout << "(" << end.first << ", " << end.second << ")" << endl;
		if(end.first == 4 && end.second == 4) break;
		end = Prev[end.first][end.second];
		
	}
}

DFS

组合型枚举:无限制数量

#include<iostream>
using namespace std;

int a[10000];
int n,sum; 

void dfs(int t,int idx,int s){
	if(s == n){
		cout << n << "=" << a[0];
		for(int i = 1; i < t; i++){
			cout << "+" << a[i];
		}
		cout << endl;
		sum++;
		return;
	}
	for(int i = idx; i < n; i++){
		if(i <= n-s+1){
			a[t] = i;
			dfs(t+1,i,s+i);
		} 
	}
}

int main(){
	cin >> n;
	dfs(0,1,0);	
	return 0;
} 

组合型枚举:有数量限制

#include<bits/stdc++.h>
using namespace std;
int a[1000];
int n,k;

void func(int m,int idx){
	if(m == k){
		for(int i = 0; i < m; i++) printf("%3d",a[i]);
		cout << endl;
	}
	for(int i = idx+1; i <= n; i++){ //idx为我上次找到元素的位置从后面继续找
		a[m] = i;
		func(m+1,i); //m+1搭配方案里的元素+1,idx=i为我现在看到的元素位置
	}
}

int main(){
	cin >> n >> k;
	func(0,0);
	return 0;
}

全排列

#include<iostream>
using namespace std;

int n;
int a[10000],v[1000000];

void dfs(int x){
	if(x == n){
		for(int i = 0; i < n; i++) cout << a[i] << " ";
		cout << endl;
	}
	for(int i = 1; i <= n; i++){
		if(v[i] == 0){
			a[x] = i;
			v[i] = 1;
			dfs(x+1);
			v[i] = 0;
		}
	}
}

int main(){
	cin >> n;
	dfs(0);
}

八皇后

#include<iostream>
using namespace std;

const int N = 1001;
char g[N][N];
bool col[N],dg[N],udg[N]; 
int n,cnt; 

void dfs(int u){
	if(u == n){
		cnt++;
		cout << "No. " << cnt << endl;
		for(int j = 0; j < n; j++){
			for(int i = 0; i < n; i++){
				if(g[j][i] == 'Q') cout << 1 << " ";
				else cout << 0 << " ";
			}
			cout << endl;
		} 
		return ;
	}
	for(int i = 0; i < n; i++){
		if(!col[i] && !dg[u+i] && !udg[n-u+i]){
			g[u][i] = 'Q';
			col[i] = dg[u+i] = udg[n-u+i] = true;
			dfs(u+1);
			col[i] = dg[u+i] = udg[n-u+i] = false;
			g[u][i] = '.';
		}
	}
} 

int main(){
	n = 8;
	for(int i = 0; i < n; i++){
		for(int j = 0; j < n; j++){
			g[i][j] = '.';
		}
	}
	dfs(0);
	return 0;
} 

图论:

        存储与遍历

               vectoer

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;

const int N = 1e3+10;
vector<vector<int> > g(N);
bool st[N];
int n,m; 

void bfs(int x){
	queue<int> q;
	q.push(x);
	st[x] = 1;
	cout << x << " ";
	while(!q.empty()){
		int t = q.front();
		q.pop();
		for(int i = 0; i < g[t].size(); i++){
			if(!st[g[t][i]]){
				q.push(g[t][i]);
				st[g[t][i]] = 1;
				cout << g[t][i] << " ";
			}
		}
	}
}

void dfs(int x){
	st[x] = 1;
	cout << x << " ";
	for(int i = 0; i < g[x].size(); i++){
		if(!st[g[x][i]]) dfs(g[x][i]);
	}
}

int main(){
	cin >> n >> m;
	for(int i = 1; i <= m; i++){
		int a,b; cin >> a >> b;
		g[a].push_back(b);
		g[b].push_back(a);
	}
	for(int i = 1; i <= n; i++){
		for(int j = 0; j < g[i].size(); j++){
			cout << i << "---->" << g[i][j] << endl;
		}
	} 
	bfs(1);
	cout << endl;
	memset(st,0,sizeof(st));
	dfs(1);
	return 0;
} 


/*
9 8
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6
*/ 

临接矩阵-数组

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;

const int N = 1e3+10;
int n,m;
int g[N][N];
int v[N];

void bfs(int x){
	queue<int> q;
	q.push(x);
	v[x] = 1;
	cout << x << " ";
	while(!q.empty()){
		int t = q.front();
		q.pop();
		for(int i = 1; i <= n; i++){
			if(g[t][i] && !v[i]){
				q.push(i);
				cout << i << " ";
				v[i] = 1;
			}
		}
	}
}

void dfs(int x){
	v[x] = 1;
	cout << x << " ";
	for(int i = 1; i <= n; i++){
		if(g[x][i] && !v[i]) dfs(i);
	}
}

int main(){
	int x; 
	cin >> n >> m;
	for(int i = 1; i <= m; i++){
		int a,b; cin >> a >> b;
		g[a][b] = g[b][a] = 1;
	}
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= n; j++){
			if(g[i][j]) cout << i << "------>" << j << endl;
		}
	} 
	bfs(1); 
	cout << endl;
	memset(v,0,sizeof v);
	dfs(1);
	return 0;
} 


/*
9 8
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6
*/ 

临接表-链表

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

const int N = 1e5 + 10, M = 1e6 + 10;
int st[N];
int n, m;
// h数组存储所有节点的边链表的头节点
// e数组存储的是节点 
int h[N], e[M], ne[M], w[M], idx;

void add(int a, int b) {
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx;
	idx++;
}

void dfs(int u) {
	cout << u << " ";
	st[u] = 1;
	for(int i = h[u]; i != -1; i = ne[i]){
		int j = e[i];
		if(!st[j]) dfs(j);
	}
} 

void bfs(int u) {
	queue<int> q;
	q.push(u);
	st[u] = 1;
	cout << u << ' ';
	while(!q.empty()){
		int t = q.front();
		q.pop();
		for(int i = h[t]; i != -1; i = ne[i]){
			int j = e[i];
			if(!st[j]){
				cout << j << " ";
				st[j] = 1;
				q.push(j);
			} 
		} 
	} 
}


int main() {
	memset(h, -1, sizeof h);
	// 链式前向星
	 
	cin >> n >> m;
	for (int i = 0; i < m; i++) {
		int a, b, c; cin >> a >> b;
		add(a, b);
		add(b, a);
	}
	
	for (int i = 1; i <= n; i++) { // 枚举每一个顶点 
		cout << i << "的所有边有这些:";  // 枚举当前顶点的所有边 
		for (int j = h[i]; j != -1; j = ne[j]) {
			cout << e[j] << " ";
		}
		cout << endl;
	}
	
	dfs(1);
	cout << endl;
	memset(st, 0, sizeof st);
	bfs(1);
	
    return 0;
}



/*
一个示例图: 无向图 
9 8
1 2
1 7
1 4
2 8
2 5
4 3
3 9
4 6
*/

拓扑排序

topsort每次只能输出一种拓扑序。

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;

const int N = 1e5;
int e[N],ne[N],h[N],idx,d[N],v[N],st[N],pre[N],cnt = 0;
int n,m; 

long long sum = 0;

void topsort(){
	queue<int> q;
	for(int i = 1; i <= n; i++){
		if(d[i] == 0){
			q.push(i);
			pre[++cnt] = i; 
		}
	}
	while(!q.empty()){
		int t = q.front();
		q.pop();
		for(int i = h[t]; i != -1; i = ne[i]){
			int j = e[i];
			if(!st[j]){
				d[j]--;
				if(d[j] == 0){
					q.push(j);
					pre[++cnt] = j; 
				}
			}
		}
	}
	for(int i = 1; i <= n; i++){
		if(d[i] > 0){
			cout << "构不成topsort";
			return ; 
		}
	}
	for(int i = 1; i <= n; i++) cout << pre[i] << " ";
	cout << endl;
}

void add(int a,int b){
	e[idx] = b;
	ne[idx] = h[a];
	h[a] = idx;
	idx++;
}

int main(){
	memset(h,-1,sizeof h);
	memset(st,0,sizeof st);
	cin >> n >> m;
	for(int i = 1; i <= m; i++){
		int a,b; cin >> a >> b;
		add(a,b); 
		d[b]++;
	}
	topsort();
}

最短路径算法

dijkstra算法(无负权,单源最短路,O(nm))

#include<bits/stdc++.h>
using namespace std;

const int N =1e5+10;
int h[N],e[N],ne[N],w[N],idx,dist[N];
int n,m;
bool s[N];

void add(int a,int b,int c){
	e[idx] = b;
	w[idx] = c;
	ne[idx] = h[a];
	h[a] = idx;
	idx++;
} 

int dijkstra(int st,int ed){
	memset(dist,0x3f,sizeof dist);
	memset(s,0,sizeof s);
	dist[st] = 0;
	for(int i = 1; i < n; i++){
		int k = -1;
		for(int j = 1; j <= n; j++){
			if(!s[j] && (k==-1 || dist[j] < dist[k])) k = j;
		}
		s[k] = 1;
		for(int j = h[k]; j != -1; j = ne[j]){
			int t = e[j];
			if(!s[t]) dist[t] = min(dist[t],dist[k]+w[j]);
		}
	}
	if(dist[ed] == 0x3f3f3f) return -1;
	else return dist[ed];
}

int main(){
	memset(h,-1,sizeof h);
	cin >> n >> m;
	for(int i = 1; i <= m; i++){
		int a,b,c; cin >> a >> b >> c;
		add(a,b,c);
	}
	int ans = dijkstra(1,n);
	cout << ans << endl;
	return 0;
} 

优化版dijkstra算法(一般O(n),最坏O(n,m))



#include<bits/stdc++.h>
using namespace std;
 
typedef pair<int,int> PII;
const int N =1e5+10;
int h[N],e[N],ne[N],w[N],idx,dist[N];
int n,m;
bool s[N];
 
void add(int a,int b,int c){
	e[idx] = b;
	w[idx] = c;
	ne[idx] = h[a];
	h[a] = idx;
	idx++;
} 
 
int dijkstra(int st,int ed){
	memset(dist,0x3f,sizeof dist);
	memset(s,0,sizeof s);
	dist[st] = 0;
	priority_queue<PII,vector<PII>,greater<PII> > heap;
	heap.push({dist[st],st});
	while(!heap.empty()){
		auto t = heap.top();
		heap.pop();
		
		int k = t.second,dis = t.first;
		if(s[k]) continue;
		s[k] = 1;
		for(int i = h[k]; i != -1; i = ne[i]){
			int j = e[i];
			if(!s[j]){
				if(dist[j] > dis+w[i]){
					dist[j] = dis+w[i];
					heap.push({dist[j],j});
				}
			}
		}
	}
	if(dist[ed] == 0x3f3f3f) return -1;
	else return dist[ed];
}
 
int main(){
	memset(h,-1,sizeof h);
	cin >> n >> m;
	for(int i = 1; i <= m; i++){
		int a,b,c; cin >> a >> b >> c;
		add(a,b,c);
	}
	int ans = dijkstra(1,n);
	cout << ans << endl;
	return 0;
} 


 

Bellman_Ford算法

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

const int N = 1e5*5;
int x,m,n,dist[N];

struct Node{
	int u,v,w;
} a[N];

void bellman_ford(int st){
	memset(dist,0x3f,sizeof(dist));
	dist[st] = 0;
	for(int i = 1; i < n; i++){
		for(int j = 1; j <= m; j++){
			auto e = a[j];
			dist[e.v] = min(dist[e.v],dist[e.u]+e.w);
		} 
	}
}

int main(){
	cin >> n >> m;
	for(int i = 1; i <= m; i++){
		int aa,b,c; cin >> aa >> b >> c; 
		a[i] = {aa,b,c};
	}
	bellman_ford(1);
	cout << dist[n] << endl;
	return 0;
}

SPFA算法

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;

const int N = 500000*2;
int h[N],w[N],e[N],ne[N],idx,n,m,dist[N],s[N];

void add(int a,int b,int c){
	e[idx] = b;
	w[idx] = c;
	ne[idx] = h[a];
	h[a] = idx;
	idx++;
}

void spfa(int st){
	memset(dist,0x3f,sizeof dist);
	dist[st] = 0;
	s[st] = 1;
	queue<int> q;
	q.push(st);
	while(!q.empty()){
		auto k = q.front();
		q.pop();
		s[k] = 0;
		for(int i = h[k]; i != -1; i = ne[i]){
			int j = e[i];
			if(dist[j] > dist[k]+w[i]){
				dist[j] = dist[k]+w[i];
				if(!s[j]){
					q.push(j);
					s[j] = 1;
				}
			}
		}
	}
} 

int main(){
	memset(h,-1,sizeof h);
	cin >> n >> m;
	for(int i = 1; i <= m; i++){
		int a,b,c; cin >> a >> b >> c;
		add(a,b,c);
		add(b,a,c); 
	}
	spfa(1);
	cout << dist[n] << endl;
	return 0;
} 

Floyd算法

#include<iostream>
using namespace std;

const int N = 1e3,INF = 0x3f3f3f3f;
int a[N][N],n,m;

void floyd(){
	for(int k = 1; k <= n; k++){
		for(int i = 1; i <= n; i++){
			for(int j = 1; j <= n; j++){
				a[i][j] = min(a[i][j],a[i][k]+a[k][j]);
			}
		}
	}
} 

int main(){
	cin >> n >> m;
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= n; j++){
			if(i == j) a[i][j] = 0;
			else a[i][j] = INF;
		}
	} 
	for(int i = 1; i <= m; i++){
		int aa,b,c; cin >> aa >> b >> c;
		a[aa][b] = a[b][aa] = c;
	}
	floyd();
	cout << a[1][n] << endl;
	return 0;
}

常见错误

1、h,没有memset(h,-1,sizeof h)初始化

2、dist,没有memset(dist,0x3f,sizeof dist)初始化

3、n,m要分清,不要写反了

并查集

查询合并操作

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

const int N = 5050;
int f[N];
int n,m,p;

int find(int x){
	if(f[x] != x) f[x] = find(f[x]);
	return f[x]; 
}

void merge(int x,int y){
	int f_x = find(x),f_y = find(y);
	if(f_x != f_y) f[f_y] = f_x; 
}

int main(){
	cin >> n >> m >> p;
	for(int i = 1; i <= n; i++) f[i] = i;
	while(m--){
		int x,y;
		cin >> x >> y;
		merge(x,y);
	
	}
	while(p--){
		int x,y;
		cin >> x >> y;
		if(find(x) == find(y)) cout << "Yes" << endl;
		else cout << "No" << endl; 
	} 
	return 0;
} 

动态规划(DP)

背包DP

01背包-二维版

01背包(背包有限制容量)指的是在一些物品(包含价值和重量.....属性)中选出每个物品最多选一件,背包总价值最大值。

#include<iostream>
using namespace std;

int n,m; 
const int N = 1e4+10;
int v[N],w[N],dp[N][N]; //w是重量,v是价值,dp(i,j)是在i个物品前,选总重量为j的物品,其最大价值。 

int main(){
	cin >> n >> m;//n是物品的件数,m是背包容积 
	for(int i = 1; i <= n; i++) cin >> w[i] >> v[i];
	for(int i = 1; i <= n; i++){
		for(int j = 0; j <= m; j++){
			dp[i][j] = dp[i-1][j];
			if(j >= w[i]) dp[i][j] = max(dp[i][j],dp[i-1][j-w[i]]+v[i]);
		}
	}
	cout << dp[n][m] << endl;
	return 0;
} 

01背包-一维优化

#include<iostream>
using namespace std;

int n,m; 
const int N = 1e4+10;
int v[N],w[N],dp[N];//w是重量,v是价值,dp(i,j)是在i个物品前,选总重量为j的物品,其最大价值。 

int main(){
	cin >> n >> m;//n是物品的件数,m是背包容积 
	for(int i = 1; i <= n; i++) cin >> w[i] >> v[i];
	for(int i = 1; i <= n; i++){
		for(int j = m; j >= w[i]; j--){
			//倒着枚举,因为每个物品只能选一件,所以是看上一层的最大价值,而不是本轮,如果是正着的话,那每个物品可以选无限件 
			dp[j] = max(dp[j],dp[j-w[i]]+v[i]); 
		}
	}
	cout << dp[m] << endl;
	return 0;
} 

完全背包-朴素二维

完全背包(背包有限制容量)是在一些物品(包含价值和重量.....属性)中选出,每个物品可以选无数件,背包总价值最大值。

#include<bits/stdc++.h>
using namespace std;

int n,m;
const int N = 1e4+10;
int v[N],w[N],dp[N][N];//w是重量,v是价值,dp(i,j)是在i个物品前,选总重量为j的物品,其最大价值。

int main(){
	cin >> n >> m;//n是物品的件数,m是背包容积 
	for(int i = 1; i <= n; i++) cin >> w[i] >> v[i];
	for(int i = 1; i <= n; i ++){
		for(int j = 0; j <= m; j++){
			for(int k = 0; k*w[i] <= j; k++){
				dp[i][j] = max(dp[i][j],dp[i-1][j-w[i]*k]+v[i]*k); //暴力枚举一个物品可以选k件(理论上说k是无限大的)。 
			}
		}
	}
	cout << dp[n][m] << endl;
}

完全背包-二维版


#include<bits/stdc++.h>
using namespace std;

int n,m;
const int N = 1e4+10;
int v[N],w[N],dp[N][N];//w是重量,v是价值,dp(i,j)是在i个物品前,选总重量为j的物品,其最大价值。

int main(){
	cin >> n >> m;//n是物品的件数,m是背包容积 
	for(int i = 1; i <= n; i++) cin >> w[i] >> v[i];
	for(int i = 1; i <= n; i ++){
		for(int j = 0; j <= m; j++){
			//两种决策,选或不选 
			dp[i][j] = dp[i-1][j]; //如果不选就是上一个状态的最大价值 
			if(w[i] <= j) dp[i][j] = max(dp[i][j],dp[i][j-w[i]]+v[i]);  //如果选就是要满足当前的物品重量要小于背包容量,比较选了大还是不选大。 
		}
	}
	cout << dp[n][m] << endl;
}

 完全背包-一维优化

#include<bits/stdc++.h>
using namespace std;

int n,m;
const int N = 1e4+10;
int v[N],w[N],dp[N];//w是重量,v是价值,dp(i,j)是在i个物品前,选总重量为j的物品,其最大价值。

int main(){
	cin >> n >> m;//n是物品的件数,m是背包容积 
	for(int i = 1; i <= n; i++) cin >> w[i] >> v[i];
	for(int i = 1; i <= n; i ++){
		for(int j = w[i]; j <= m; j++){
			dp[j] = max(dp[j],dp[j-w[i]]+v[i]); //从前向后遍历因为可以包含多件物品,是当前这一层循环的生成结果 
		}
	}
	cout << dp[m] << endl;
}

多重背包-朴素版

多重背包(背包有限制容量)指的是在一些物品(包含价值和重量.....属性)中选出每个物品可以选一定的件数(题目中会给),背包总价值最大值。

#include<iostream>
using namespace std;

const int N = 1e4+10;
int v[N],w[N],s[N]; //v表示价值,w表示重量(质量),s表示第i件物品的数量 
int dp[N][N];

int main(){
	int n,m;
	cin >> n >> m; //n是物品的件数,m是背包容积 
	for(int i = 1; i <= n; i++) cin >> w[i] >> v[i] >> s[i]; 
	for(int i = 1; i <= n; i++){
		for(int j = 0; j <= m; j++){
			for(int k = 0; k <= s[i] && k*w[i] <= j; k++){
				dp[i][j] = max(dp[i][j],dp[i-1][j-w[i]*k]+v[i]*k); //跟完全背包朴素版有点像,就是枚举一下每个物品的件数 
			}
		}
	}
	cout << dp[n][m] << endl;
	return 0;
}

多重背包-二进制优化版

#include<iostream>
using namespace std;

const int N = 1e5+10;
int n,m;
int cnt = 0;
int v[N],w[N]; //v表示价值,w表示重量(质量); 
int dp[N];

int main(){
	cin >> n >> m;
	//这个位置的话处理就是把多个物品可以看成一组,但是通过多组可以组合成任意一个数;
	//比如当前这件物品的数量是4,那么1,2,1这个方法可以组合出来1,2,3,4任意的数。
	//但是这种方法又减少了枚举次数,优化了时间 
	for(int i = 1; i <= n; i++){
		int a,b,s;
		int k = 1;
		cin >> a >> b >> s;
		while(k <= s){
			cnt++;
			w[cnt] = a*k;  
			v[cnt] = b*k;
			s -= k;
			k *= 2;
		}
		if(s > 0){
			cnt++;
			w[cnt] = a*s;
			v[cnt] = b*s;
		}
	}
	n = cnt;
	for(int i = 1; i <= n; i++){
		for(int j = m; j >= w[i]; j--){
			dp[j] = max(dp[j],dp[j-w[i]]+v[i]); 
		}
	}
	cout << dp[m] << endl;
	return 0;
}

分组背包-二维版

        分组背包(背包有限制容量)是在一些物品(包含价值和重量.....属性)中选出,每个组中只可以选一件,背包总价值最大值。

        在分组背包中的i可以理解成前i组而不是其他背包中的前i个物品。

#include<bits/stdc++.h>
using namespace std;

const int N=1e4+10;
int dp[N][N];  //只从前i组物品中选,当前体积小于等于j的最大值
int v[N][N],w[N][N],s[N];   //w为体积,v为价值,s代表第i组物品的个数
int n,m,k;

int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>s[i];
        for(int j=0;j<s[i];j++){
            cin>>w[i][j]>>v[i][j];  //读入
        }
    }

    for(int i=1;i<=n;i++){
        for(int j=0;j<=m;j++){
            dp[i][j]=dp[i-1][j];  //不选
            for(int k=0;k<s[i];k++){
                if(j>=w[i][k])  dp[i][j]=max(dp[i][j],dp[i-1][j-w[i][k]]+v[i][k]);  
            }
        }
    }
    cout<<dp[n][m]<<endl;
}

分组背包-一维优化

#include<bits/stdc++.h>
using namespace std;

const int N = 1e4+10;
int n,m;
int v[N][N],w[N][N],s[N],dp[N]; //w为体积,v为价值,s代表第i组物品的个数,dp[i][j]只从前i组物品中选,当前体积小于等于j的最大值

int main(){
	cin >> n >> m;
	for(int i = 1; i <= n; i++){
		cin >> s[i];
		for(int j = 0; j < s[i]; j++) cin >> w[i][j] >> v[i][j];
	}
	for(int i = 1; i <= n; i++){
		for(int j = m; j >= 0; j--){
			for(int k = 0; k < s[i]; k++){
				if(w[i][k] <= j) dp[j] = max(dp[j],dp[j-w[i][k]]+v[i][k]); //遍历组的每个物品,选择最优 
			}
		}
	}
	cout << dp[m] << endl;
	return 0;
} 

线性DP

数字三角形(数字金子塔)

        y总的acwing上的题有点坑,他是有负数的(一般的数字三角形这种题是不含负数的),所以要初始化一下为-INF,这道题的思路很简单。给出一下两种的代码:正推,逆推。

        第一种是正推:自顶向下推。也就是要知道顶从顶向下推进。

        第二种是逆推:自底向上推。也是知道底从底向上推进。

         每次推进有两个方向,也就是可以通过这个两个点到达当前点,取max+当前点的价值即可;

//#include<iostream>
//using namespace std;
//
//const int N = 1e4+10,INF = 1e9;
//int dp[N][N],a[N][N];
//
//int main(){
//	int n; cin >> n;
//	for(int i = 1; i <= n; i++){
//		for(int j = 1; j <= i; j++){
//			cin >> a[i][j];
//		}
//	} 
//	for(int i = 0; i <= n; i++){
//		for(int j = 0; j <= i+1; j++) dp[i][j] = -INF; //这里i+1每行多初始化一个用于处理边界 
//	}
//	dp[1][1] = a[1][1];
//	for(int i = 2; i <= n; i++){
//		for(int j = 1; j <= i; j++){
//			dp[i][j] = max(dp[i-1][j],dp[i-1][j-1])+a[i][j];
//		}
//	}
//	int mx = -1e9;
//	for(int i = 1; i <= n; i++) mx = max(dp[n][i],mx); 
//	cout << mx << endl;
//	return 0;
//} 


#include<iostream>
using namespace std;

const int N = 1e4+10,INF = 1e9;
int a[N][N],dp[N][N];

int main(){
	int n; cin >> n;
	for(int i = 1; i <= n; i++){
		for(int j = 1; j <= i; j++){
			cin >> a[i][j];
			dp[i][j] = -INF;
		}
	} 
	for(int i = n; i >= 1; i--){
		for(int j = 1; j <= i; j++){
			if(i == n) dp[i][j] = a[i][j];
			else dp[i][j] = max(dp[i+1][j],dp[i+1][j+1])+a[i][j];
		}
	}
	cout << dp[1][1] << endl;
	return 0;
}

 最长上升子序列

        很经典的DP题,其变形还有最长不下降子序列,最长不上升子序列,最长下降子序列...所以要牢牢掌握。

        dp[i]表示以i结尾的最长上升子序列的长度。思路的话就是求前面的数是否满足要求如a[j] < a[i]就满足上升子序列,所以要max一下是加上这个数子序列长还是不加上长。最后对DP数组取一下max即可。

        在这种问题中有时候还会让你记录下选的方案,那么就记录下来状态转移的过程。

不记录方案版:

#include<iostream>
using namespace std;

const int N = 1e5+10,INF = 1e9;
int a[N],dp[N]; //dp[i]表示以第i个数结尾的最长上升子序列长度 

int main(){
	int n,mx = -INF; cin >> n;
	for(int i = 1; i <= n; i++) cin >> a[i];
	for(int i = 1; i <= n; i++){
		dp[i] = 1;
		g[i] = 0;
		for(int j = 1; j < i; j++){
			//这里的运算符就是表示是什么子序列,如果是<就是上升子序列,<=话是不下降子序列,>是下降子序列,>=不上升子序列 
			if(a[j] < a[i]){ 
				dp[i] = max(dp[i],dp[j]+1); //看一下是选了这个数长,还是不选长。 
			}
		}
		mx = max(mx,dp[i]);
	}
	cout << mx << endl;
	return 0;
}

记录方案版:

#include<iostream>
using namespace std;

const int N = 1e5+10,INF = 1e9;
int a[N],dp[N],g[N];

int main(){
	int n,mx = -INF; cin >> n;
	for(int i = 1; i <= n; i++) cin >> a[i];
	for(int i = 1; i <= n; i++){
		dp[i] = 1;
		g[i] = 0;
		for(int j = 1; j < i; j++){
			if(a[j] < a[i]){
				if(dp[i] < dp[j]+1){
					dp[i] = dp[j]+1;
					g[i] = j; //存下来状态转移过程 
				}
			}
		}
	}
	int k = 1;
	for(int i = 1; i <= n; i++){
		if(dp[k] < dp[i]) k = i;
	}
	for(int i = 0,len = dp[k]; i < len; i++){
		printf("%d ",a[k]);
		k = g[k]; //倒序的,如果正序可以存下来反向输出,或者是用dfs输出 
	}
	return 0;
}

最长公共子序列

      dp[i][j]表示在第一个序列中前i个字母中出现,并且在第二个序列前j个字母中出现的子序列。

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

const int N = 10001;
int f[N][N];
int n,m; 

int main(){
	cin >> n >> m; 
	string a,b;
	cin >> a >> b;
	for(int i = 1; i <= a.size(); i++){
		for(int j = 1; j <= b.size(); j++){
			if(a[i-1] == b[j-1]) f[i][j] = f[i-1][j-1]+1;
			else f[i][j] = max(f[i-1][j],f[i][j-1]);
		}
	} 
	printf("%d\n",f[a.size()][b.size()]);
	return 0;
}

区间DP

合并石子

#include<iostream>
using namespace std;

const int N = 1e4+10;
int n; 
int s[N],dp[N][N]; //dp[i,j]表示从第i个石子到第j个石子合并最小体力 

int main(){
	cin >> n;
	for(int i = 1; i <= n; i++) cin >> s[i];
	for(int i = 1; i <= n; i++) s[i] += s[i-1];
	for(int len = 2; len <= n; len++){ //是1的话合并不了,所以可以直接从区间长度为2开始 
		for(int i = 1; i+len-1 <= n; i++){ //枚举区间长度 
			int l = i, r = i+len-1;
			dp[l][r] = 1e9;
			for(int k = l; k < r; k++){
				dp[l][r] = min(dp[l][r],dp[l][k]+dp[k+1][r]+s[r]-s[l-1]); 
				//dp[l][r]可以等价变形于dp[l][k]+dp[k+1][r]+s[r]-s[l-1]首先是l-k的最小体力,然后是k+1-r的最小体力,
				//最后的 s[r]-s[l-1]相当于求l->r区间的石子重量和,再拿这个新的最小体力和之前的最小体力对比 
			} 
		}
	}
	cout << dp[1][n] << endl;
	return 0;
} 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值