基础算法
快速排序
#include<iostream>
using namespace std;
int a[1000000] = { 0 };
void quick_sort(int q[], int l, int r)
{
if (l >= r) return;
int i = l - 1, j = r + 1, x = q[l + r >> 1];
while (i < j)
{
do i++; while (q[i] < x);
do j--; while (q[j] > x);
if (i < j) swap(q[i], q[j]);
}
quick_sort(q, l, j), quick_sort(q, j + 1, r);
}
int main() {
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++)
scanf("%d", &a[i]);
quick_sort(a, 0, n - 1);
for (int j = 0; j < n; j++)
cout << a[j] << " ";
}
一般是直接调用sort函数
寻找数列从小到大排序后的第k个数:依照快速排序的思路,每次排序都会得到左边和右边的数量。
int n,k,ans;
void findk(int q[],int l,int r){
if(l>=r){
ans=q[l];
return;
}
int i=l-1,j=r+1,x=q[l+r>>1];
while(i<j){
do i++;while(q[i]<x);
do j--;while(q[j]>x);
if(i<j)swap(q[i],q[j]);
}
if(k<=j)return findk(q,l,j);
else return findk(q,j+1,r);
}
归并排序
分而治之
#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int q[N], tmp[N];
void guibin(int q[],int l,int r) {
if (l >= r)return;
int mid= l + r >> 1;//定义为中间的数。
guibin(q, l, mid);//处理左边
guibin(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++];
}
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];//将结果存入q数组
}
int main() {
int n;
cin >> n;
for (int i = 0; i < n; i++)
scanf("%d", &q[i]);
guibin(q, 0, n - 1);
for (int i = 0; i < n; i++)
printf("%d ", q[i]);
}
经典题目:逆序对的数量
void guibin(int q[],int l,int r) {
if (l >= r)return;
int mid= l + r >> 1;//定义为中间的数。
guibin(q, l, mid);//处理左边
guibin(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++];
ans+=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];//将结果存入q数组
}
二分
查找不小于x的第一个位置:
int l=0,r=n-1;
while(l<r){
int mid=l+r>>1;
if(a[mid]<x)l=mid+1;
else r=mid;
}
查找不大于x的最后一个位置
int l=0,r=n-1;
while(l<r){
int mid=l+r+1>>1;
if(a[mid]<=x)l=mid;
else r=mid-1;
}
其中c++自带有二分函数,分别为lower_bound()、upper_bound()以及binary_search():
lower_bound():返回大于或等于目标值的第一个位置
lower_bound(a + begin, a + end, k, cmp);
功能:在数组a中从a[begin]开始到a[end - 1]按照cmp函数来比较进行二分查找第一个大于等于k的数的位置,如果有第一个大于等于k的数则返回该数的地址,否则返回a[end]的地址。
upper_bound():返回大于目标值的第一个位置
binary_search():若目标值存在则返回true,否则返回false
binary_search(a+1,a+n+1,x,cmp); //数组a存放一个有序序列,1是起始地址,n+1是结束地址,x是待查找的数值,cmp多用于对结构体的查找
#include<bits/stdc++.h>
using namespace std;
int main(){
int a[5]={1,2,3,4,6};
cout<<">=5的数的第一个位置"<<(lower_bound(a,a+5,5)-a)<<endl;
cout<<">=4的数的第一个位置"<<(lower_bound(a,a+5,4)-a)<<endl;
cout<<">3的数的第一个位置"<<(upper_bound(a,a+5,3)-a)<<endl;
}
//运行结果
//>=5的数的第一个位置4
//>=4的数的第一个位置3
//>3的数的第一个位置3
前缀和
一维前缀和:
//预处理
for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i];
//求[l,r]之间的数的和
ans=a[r]-a[l-1];
二维前缀和:
//预处理
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
//求a[x1][y1]到a[x2][y2]之间的和
ans=s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1];
差分
一维差分:
//构建差分数组
for(int i=1;i<=n;i++)
b[i]=a[i]-[a-1];
//将[l,r]中的每个数都加上c
b[l]+=c;
b[r+1]-=c;
二维差分
int a[N][N],b[N][N];
//预处理
void insert(int x1,int y1,int x2,int y2,int c){
b[x1][y1]+=c;
b[x2+1][y1]-=c;
b[x1][y2+1]-=c;
b[x2+1][y2+1]+=c;
}
//得到原数组
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
b[i][j]=b[i-1][j]=b[i][j-1]-b[i-1][j-1];
状态压缩
取出整数n在二进制表示下的第k位:(n>>k)&1
取出整数n在二进制表示下的第0~k-1位(后k位):n&((1<<k)-1)
把整数n在二进制表示下的第k位取反:n^(1<<k)
对整数n在二进制表示下的第k位赋值为1:n|(1<<k)
对整数n在二进制表示下的第k位赋值为0:n&(~(1<<k))
二进制中1的个数
#include<iostream>
using namespace std;
const int N=1e5+5;
long long lowbit(long long x){
return x&-x;
}
int main(){
int n;
cin>>n;
while(n--){
long long x;
scanf("%lld",&x);
int ans=0;
while(x){
x-=lowbit(x);
ans++;
}
cout<<ans<<" ";
}
return 0;
}
离散化
排序+去重
//第一种
void test(){
sort(a+1,a+n+1);//排序
for(int i=1;i<=n;i++)
if(i==1||a[i]!=a[i-1])
b[++m]=a[i];
}
//第二种
void test(){
sort(a.begin(),a.end());//排序
a.erase(unique(a.begin(),a.end()),a.end());//去重
}
数据结构
单链表
#include <iostream>
using namespace std;
const int N = 100010;
int n;
int h[N], e[N], ne[N], head, idx;
//对链表进行初始化
void init(){
head = -1;//最开始的时候,链表的头节点要指向-1,
idx = 0;
}
//将x插入到头节点上
void int_to_head(int x){//和链表中间插入的区别就在于它有head头节点
e[idx] = x;//第一步,先将值放进去
ne[idx] = head;//head作为一个指针指向空节点,现在ne[idx] = head;做这把交椅的人换了
//先在只是做到了第一步,将元素x的指针指向了head原本指向的
head = idx;//head现在表示指向第一个元素了,它不在是空指针了。(不指向空气了)
idx ++;//指针向下移一位,为下一次插入元素做准备。
}
//将x插入到下标为k的点的后面
void add(int k, int x){
e[idx] = x;//先将元素插进去
ne[idx] = ne[k];//让元素x配套的指针,指向它要占位的元素的下一个位置
ne[k] = idx;//让原来元素的指针指向自己
idx ++;//将idx向后挪
}
//将下标是k的点后面的点个删掉
void remove(int k){
ne[k] = ne[ne[k]];//让k的指针指向,k下一个人的下一个人,那中间的那位就被挤掉了。
}
int main(){
cin >> n;
init();//初始化
for (int i = 0; i < n; i ++ ) {
char s;
cin >> s;
if (s == 'H') {
int x;
cin >> x;
int_to_head(x);
}
if (s == 'D'){
int k;
cin >> k;
if (k == 0) head = ne[head];//删除头节点
else remove(k - 1);//注意删除第k个输入后面的数,那函数里放的是下标,k要减去1
}
if (s == 'I'){
int k, x;
cin >> k >> x;
add(k - 1, x);//同样的,第k个数,和下标不同,所以要减1
}
}
for (int i = head; i != -1; i = ne[i]) cout << e[i] << ' ' ;
cout << endl;
return 0;
}
双链表
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int m;
int e[N], l[N], r[N];
int idx;
//! 初始化
void init()
{
l[1] = 0, r[0] = 1;//* 初始化 第一个点的右边是 1 第二个点的左边是 0
idx = 2;//! idx 此时已经用掉两个点了
}
//* 在第 K 个点右边插入一个 X
void add(int k, int x)
{
e[idx] = x;
l[idx] = k;
r[idx] = r[k]; //todo 这边的 k 不加 1 , 输入的时候 k+1 就好
l[r[k]] = idx;
r[k] = idx;
idx++;
}//! 当然在 K 的左边插入一个数 可以再写一个 , 也可以直接调用我们这个函数,在 k 的左边插入一个 数 等价于在 l[k] 的右边插入一个数 add(l[k],x)
//*删除第 k个 点
void remove(int k)
{
r[l[k]] = r[k];
l[r[k]] = l[k];
}
int main(void)
{
ios::sync_with_stdio(false);
cin >> m;
init();
while(m--)
{
string op;
cin >> op;
int k, x;
if(op=="R")
{
cin >> x;
add(l[1], x); //! 0和 1 只是代表 头和尾 所以 最右边插入 只要在 指向 1的 那个点的右边插入就可以了
}
else if(op=="L")//! 同理 最左边插入就是 在指向 0的数的左边插入就可以了 也就是可以直接在 0的 有右边插入
{
cin >> x;
add(0, x);
}
else if(op=="D")
{
cin >> k;
remove(k + 1);
}
else if(op=="IL")
{
cin >> k >> x;
add(l[k + 1], x);
}
else
{
cin >> k >> x;
add(k + 1, x);
}
}
for(int i = r[0]; i != 1; i = r[i]) cout << e[i] << ' ';
return 0;
}
栈
模拟栈
#include<iostream>
#include<string>
using namespace std;
const int N = 1e5 + 10;
int stk[N],tt;
//从栈顶弹出一个数
void pop() {
tt--;
}
bool empty() {
if (tt > 0)cout << "NO" << endl;
else cout<<"YES"<<endl;
}
void query() {
cout << stk[tt] << endl;
}
void push(int x) {
stk[++tt] = x;
}
int main() {
int n,x;
ios::sync_with_stdio(false);
cin >> n;
string b;
while (n--) {
cin >> b;
if (b == "push") {
cin >> x;
push(x);
}
else if (b == "empty")
empty();
else if (b == "query")
query();
else pop();
}
}
使用c++中自带的stack
使用头文件include<stack>
定义stack<int>q;
q.push_back(x):将x压入栈顶
q.top():返回栈顶的元素
q.pop():删除栈顶的元素
q.size():返回栈中元素的个数
q.empty():判断栈中是否为空
队列
模拟队列
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int ll = 0, rr = 1,a[N];
void push(int x) {
a[rr++] = x;
}
void pop() {
ll++;
}
void empty() {
if (rr - ll == 1)cout << "YES" << endl;
else cout << "NO" << endl;
}
void query() {
cout << a[ll + 1] << endl;
}
int main() {
int n;
cin >> n;
string b;
int x;
while (n--) {
cin >> b;
if (b == "push") {
scanf("%d", &x);
push(x);
}
else if (b == "empty")
empty();
else if (b == "query")
query();
else pop();
}
}
c++中自带的队列函数
头文件:include<queue>
queue<int>q;
q.push(x):在队尾插入x
q.pop():删除队列第一个元素
q.size():返回队列中元素的个数
q.empty():如何队列空则返回true
q.front():返回队列中的第一个元素
q.back():返回队列中最后一个元素
单调栈
给定一个长度为 N 的整数数列,输出每个数左边第一个比它小的数,如果不存在则输出 −1。
#include<iostream>
using namespace std;
const int N=1e5+5;
int a[N];
int main(){
int n;
cin>>n;
int tt=0;
for(int i=0;i<n;i++){
int x;
scanf("%d",&x);
while(tt&&a[tt]>=x)tt--;
if(tt)cout<<a[tt]<<" ";
else cout<<"-1"<<" ";
a[++tt]=x;
}
return 0;
}
单调栈的简单使用
#include<iostream>
#include<stack>
#include<vector>
using namespace std;
int a[1010];
int main(){
int n;
cin>>n;
vector<int>xiao(n+1);
stack<int>s;
for(int i=0;i<n;i++){
cin>>a[i];
}
//找出a[i]左边的第一个比它小的值的下标
for(int i=0;i<n;i++){
while(!s.empty()&&a[i]<=a[s.top()])s.pop();
if(s.empty())xiao[i]=-1;
else xiao[i]=s.top()+1;
s.push(i);
}
for(int i=0;i<n;i++)cout<<xiao[i]<<" ";
//当不存在时,用-1表示
//5
//2 1 3 5 4
//-1 -1 2 3 3
//其他情况可以根据实际情况进行改变
return 0;
}
#include <iostream>
#include<vector>
#include<stack>
#include<cmath>
using namespace std;
int a[1000010];
int main()
{
int n;
cin>>n;
if(n==1){
int x;
cin>>x;
cout<<x;
return 0;
}
vector<int>xiaol(n+1),xiaor(n+1);
stack<int>s;
// for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
while(!s.empty()&&a[i]<=a[s.top()])s.pop();
if(s.empty())xiaol[i]=0;
else xiaol[i]=s.top();
s.push(i);
}
stack<int>t;
for(int i=n;i>=1;i--){
while(!t.empty()&&a[i]<=a[t.top()])t.pop();
if(t.empty())xiaor[i]=0;
else xiaor[i]=t.top();
t.push(i);
}
long long ans=0;
for(int i=1;i<=n;i++)
ans=max(ans,(long long)(xiaor[i]-xiaol[i]-1)*a[i]);
cout<<ans<<endl;
return 0;
}
单调队列
#include<iostream>
using namespace std;
const int N = 1e6 + 10;
int a[N], b[N], hh = 0, tt = -1;
#define fir(i,j)for(int i=0;i<j;i++)
int main() {
int n,k;
cin >> n>>k;
fir(i, n) {
scanf("%d",&a[i]);
if (i - k + 1 > b[hh])++hh;
while (hh <= tt && a[i] <= a[b[tt]])--tt;
b[++tt] = i;
if (i + 1 >= k)printf("%d ",a[b[hh]]);
}
cout << endl;
hh = 0, tt = -1;
fir(i, n) {
if (i - k + 1 > b[hh])++hh;
while (hh <= tt && a[i]>=a[b[tt]])--tt;
b[++tt] = i;
if (i + 1 >= k)printf("%d ",a[b[hh]]);
}
}
KMP
//先求next数组,如何匹配字符串
#include<iostream>
using namespace std;
const int N=1e5+10,M=1e6+10;
char a[N],b[M];
int ne[N];
int main(){
int n,m;
cin>>n>>a+1>>m>>b+1;
for(int i=2,j=0;i<=n;i++){
while(j&&a[i]!=a[j+1])j=ne[j];
if(a[i]==a[j+1])j++;
ne[i]=j;
}
for(int i=1,j=0;i<=m;i++){
while(j&&b[i]!=a[j+1])j=ne[j];
if(b[i]==a[j+1])j++;
if(j==n){
cout<<i-n<<' ';
j=ne[j];
}
}
}
Trie
方式1
#include<iostream>
using namespace std;
const int N=100010;
int son[N][26],cnt[N],idx=0;
char str[N];
void insert(char*str){
int p=0;
for(int i=0;str[i];i++){
int u=str[i]-'a';
if(!son[p][u])son[p][u]=++idx;
p=son[p][u];
}
cnt[p]++;
}
int query(char *str){
int p=0;
for(int i=0;str[i];i++){
int u=str[i]-'a';
if(!son[p][u])return 0;
else p=son[p][u];
}
return cnt[p];
}
int main(){
int n;
cin>>n;
while(n--){
char x;
cin>>x;
cin>>str;
if(x=='I')insert(str);
else cout<<query(str)<<endl;
}
}
方式2
#include<iostream>
#include<map>
#include<string>
using namespace std;
int main(){
int t;
cin>>t;
map<string,int>q;
while(t--){
char c;
string s;
cin>>c>>s;
if(c=='I'){
q[s]=q[s]+1;;
}else{
cout<<q[s]<<endl;
}
}
return 0;
}
并查集
核心操作
int p[N];
void init(){//初始化
for(int i=1;i<=n;i++)p[i]=i;
}
int find(int x){//寻炸根源节点
if(p[x]!=x)p[x]=find(p[x]);
return p[x];
}
void merge(int x,int y){//将x节点和y节点放在一个集合当中
int xx=find(x);
int yy=find(y);
if(xx!=yy)p[xx]=yy;
}
堆
堆排序
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int h[N],num;
void down(int x) {
int t = x;
if (2 * x <= num && h[t] >h[x * 2])t = x * 2;
if (2 * x +1<= num && h[t] >h[x * 2+1])t = x * 2+1;
if (t != x) {
swap(h[t], h[x]);
down(t);
}
}
int main() {
int n, m;
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)scanf("%d", &h[i]);
num = n;
for (int i = n / 2; i; i--)down(i);
while (m--) {
printf("%d ", h[1]);
h[1] = h[num];
num--;
down(1);
}
}
模拟堆
#include<string.h>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int h[N],ph[N],hp[N],num;
void heap_swap(int a,int b){
swap(ph[hp[a]],ph[hp[b]]);
swap(hp[a],hp[b]);
swap(h[a],h[b]);
}
void down(int x) {
int t = x;
if (2 * x <= num && h[t] >h[x * 2])t = x * 2;
if (2 * x +1<= num && h[t] >h[x * 2+1])t = x * 2+1;
if (t != x) {
heap_swap(t,x);
down(t);
}
}
void up(int x){
while(x/2&&h[x/2]>h[x]){
heap_swap(x/2,x);
x/=2;
}
}
int main() {
int n, m=0;
scanf("%d",&n);
int k,x;
while(n--){
char op[10];
scanf("%s",op);
if(!strcmp(op,"I")){
scanf("%d",&x);
num++;m++;
ph[m]=num,hp[num]=m;
h[num]=x;
up(num);
}
else if(!strcmp(op,"PM")){
printf("%d\n",h[1]);
}
else if(!strcmp(op,"DM")){
heap_swap(1,num);
num--;
down(1);
}
else if(!strcmp(op,"D")){
scanf("%d",&k);
k=ph[k];
heap_swap(k,num);
num--;
down(k),up(k);
}
else{
scanf("%d%d",&k,&x);
k=ph[k];
h[k]=x;
down(k),up(k);
}
}
}
定义大根堆
priority_queue<int,vector<int>>s;
定义小根堆
priority_queue<int,vector<int>,greater<int>>s;
堆的操作:
top():返回堆顶元素
push():插入一个元素
size():返回元素个数
empty():判断是否为空
哈希表
模拟散列表
第一种
#include<iostream>
#include<cstring>
using namespace std;
const int N=2e5+3,null=0x3f3f3f3f;
int h[N];
int find(int x){
int k=(x%N+N)%N;
while(h[k]!=null&&h[k]!=x){
k++;
if(k==N)k=0;
}
return k;
}
int main(){
int n;
cin>>n;
memset(h,0x3f,sizeof h);
string op;
int x;
while(n--){
cin>>op>>x;
int k=find(x);
if(op=="I")h[k]=x;
else{
if(h[k]!=null)puts("Yes");
else puts("No");
}
}
}
第二种
#include<iostream>
#include<map>
using namespace std;
int main(){
int n;
cin>>n;
map<int,int>s;
while(n--){
char x;
int y;
cin>>x>>y;
if(x=='I')s[y]=1;
else{
if(s[y])puts("Yes");
else puts("No");
}
}
return 0;
}
常用到的一些操作
pair<type,type>name 可以将两个不同类型的变量存储在一起,类似于map,而其调用方式为name.first:取第一个位置的元素,name,second:取第二个位置上的元素
通常会使用
typedef pair<type,type> 新的名字,将一种变量取别名
然后可以使用在vector,queue等容器中。
搜索与图论
DFS
排列组合,输出组合数
#include<iostream>
using namespace std;
const int N = 10;
int n;
int path[N];
bool st[N];
void dfs(int u) {
if (u == n) {
for (int i = 0; i < n; i++)printf("%d ", path[i]);
printf("\n");
return;
}
for (int i = 1; i <= n; i++) {
if (!st[i]) {
path[u] = i;
st[i] = true;
dfs(u + 1);
st[i] = false;
}
}
}
int main() {
cin >> n;
dfs(0);
}
也可以调用c++中的函数next_permutation
#include<iostream>
#include<algorithm>
using namespace std;
int main() {
int n;
cin >> n;
int a[1000];
for (int i = 1; i <= n; i++) {
a[i] = i;
}
do {
for (int i = 1; i <= n; i++)printf("%d ", a[i]);
printf("\n");
} while (next_permutation(a + 1, a + 1 + n));
}
BFS
经典问题:走迷宫
直接调用stl中的queue
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
const int N = 110;
int n, m;
// 坐标位移
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, 1, 0, -1};
int g[N][N]; // 存原始地图
int d[N][N]; // 存每个点到起始点的距离
// PII pre[N][N]; // 输出路径用
int bfs() {
// 起点入队
queue<PII> q;
PII t = {0, 0};
q.push(t);
// 队列不为空,一直循环
while(!q.empty()) {
// 取出并保存队头
auto pp = q.front();
// 删除
q.pop();
// 四个方向循环判断一下
for(int i = 0; i < 4; i++) {
int x = pp.first + dx[i];
int y = pp.second + dy[i];
if(x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1) {
// 记录答案
d[x][y] = d[pp.first][pp.second] + 1;
// pre[x][y] = pp; // 输出路径用
// 扩展队头
q.push({x,y});
}
}
}
// 输出路径
/*
int x = n - 1;
int y = m - 1;
while(x || y) {
cout << x << ' ' << y << endl;
auto t = pre[x][y];
x = t.first;
y = t.second;
}
*/
// 输出答案
return d[n - 1][m - 1];
}
int main() {
cin >> n >> m;
// 读入地图
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
cin >> g[i][j];
// 初始化
memset(d, -1, sizeof(d));
d[0][0] = 0;
cout << bfs() << endl;
return 0;
}
手动模拟队列写法
#include<bits/stdc++.h>
using namespace std;
const int N=110;
typedef pair<int,int>PII;
int d[N][N];
int g[N][N];
PII q[N*N];
int n,m;
int bfs(){
int hh=0,tt=0;
memset(d,-1,sizeof d);
d[0][0]=0;
q[0]={0,0};
int dx[]={0,-1,0,1},dy[]={1,0,-1,0};
while(hh<=tt){
auto c=q[hh++];
for(int i=0;i<4;i++){
int x=c.first+dx[i];
int y=c.second+dy[i];
if(x>=0&&x<n&&y>=0&&y<m&&g[x][y]==0&&d[x][y]==-1){
d[x][y]=d[c.first][c.second]+1;
q[++tt]={x,y};
}
}
}
return d[n-1][m-1];
}
int main(){
cin>>n>>m;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
cin>>g[i][j];
cout<<bfs()<<endl;
return 0;
}
树和图的深度优先遍历
数组建立邻接表
//邻接表
int h[N], e[N * 2], ne[N * 2], idx;void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
树的dfs模板
// 需要标记数组st[N], 遍历节点的每个相邻的便
void dfs(int u) {
st[u] = true; // 标记一下,记录为已经被搜索过了,下面进行搜索过程
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
if (!st[j]) {
dfs(j);
}
}
}
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1e5+10;
const int M=N*2;
int h[N];
int e[M];
int ne[M];
int idx;
int n;
int ans=N;
bool st[N];
void add(int a,int b){
e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
int dfs(int u){
int res=0;
st[u]=true;
int sum=1;
for(int i=h[u];i!=-1;i=ne[i]){
int j=e[i];
if(!st[j]){
int s=dfs(j);
res=max(res,s);
sum+=s;
}
}
res=max(res,n-sum);
ans=min(res,ans);
return sum;
}
int main(){
memset(h,-1,sizeof h);
cin>>n;
for(int i=0;i<n-1;i++){
int a,b;
cin>>a>>b;
add(a,b),add(b,a);
}
dfs(1);
cout<<ans<<endl;
}
树和图的广度优先遍历
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5+10;
int h[N],e[N],ne[N],idx;
int q[N],d[N];
int n,m;
void add(int a,int b){
e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
int bfs(){
int hh=0,tt=0;
memset(d,-1,sizeof d);
q[0]=1;
d[1]=0;
while(hh<=tt){
int t=q[hh++];
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
if(d[j]==-1){
d[j]=d[t]+1;
q[++tt]=j;
}
}
}
return d[n];
}
int main(){
cin>>n>>m;
memset(h,-1,sizeof h);
for(int i=1;i<=m;i++){
int a,b;
scanf("%d%d",&a,&b);
add(a,b);
}
cout<<bfs()<<endl;
return 0;
}
拓扑排序
#include<iostream>
#include<cmath>
#include<cstring>
const int N=1e5+10;
int h[N],e[N],ne[N],idx;
int q[N],d[N];
int n,m;
void add(int a,int b){
e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
bool topsort(){
int hh=0,tt=0;
for(int i=1;i<=n;i++)
if(!d[i])q[++tt]=i;
while(hh<=tt){
int t=q[hh++];
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
d[j]--;
if(d[j]==0)q[++tt]=j;
}
}
return n==tt;
}
using namespace std;
int main(){
cin>>n>>m;
memset(h,-1,sizeof h);
for(int i=0;i<m;i++){
int a,b;
scanf("%d%d",&a,&b);
add(a,b);
d[b]++;
}
if(topsort()){
for(int i=1;i<=n;i++)
printf("%d ",q[i]);
puts("");
}else{
puts("-1");
}
}
Dijkstra
(适合稠密图)
朴素做法:以起点为开始,每次查找当前距离最近的点,然后以该点更新到其它点的距离。
时间复杂度O(n^2)
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
const int N=510;
int g[N][N];
int dist[N];
int n,m;
bool st[N];
int dijkstra(){
memset(dist,0x3f,sizeof dist);
dist[1]=0;
for(int i=0;i<n;i++){//一共进行n次
int t=-1;
for(int j=1;j<=n;j++)//寻找当前没走过且最近的点
if(!st[j]&&(t==-1||dist[t]>dist[j]))
t=j;
st[t]=true;//标记为走过
for(int j=1;j<=n;j++)//用该点更新到其他点的距离
dist[j]=min(dist[j],dist[t]+g[t][j]);
}
if(dist[n]==0x3f3f3f3f)return -1;
else return dist[n];
}
int main(){
cin>>n>>m;
memset(g,0x3f,sizeof g);
while(m--){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
g[a][b]=min(g[a][b],c);
}
cout<<dijkstra()<<endl;
return 0;
}
堆优化做法(适合稀疏图)
时间复杂度:O(mlogn)
#include<iostream>
#include<vector>
#include<cmath>
#include<algorithm>
#include<bits/stdc++.h>
const int N=2e5+5;
using namespace std;
int h[N],e[N],ne[N],w[N],idx;
typedef pair<int,int>PII;
int dist[N];
bool st[N];
int n,m;
void add(int a,int b,int c){
w[idx]=c;e[idx]=b;ne[idx]=h[a];h[a]=idx++;
}
int dijkstra(){
memset(dist,0x3f,sizeof dist);
dist[1]=0;
priority_queue<PII,vector<PII>,greater<PII>>heap;
heap.push({0,1});
while(heap.size()){
PII t=heap.top();
heap.pop();
int ver=t.second,distance=t.first;
if(st[ver])continue;
st[ver]=true;
for(int i=h[ver];i!=-1;i=ne[i]){
int j=e[i];
if(dist[j]>distance+w[i]){
dist[j]=distance+w[i];
heap.push({dist[j],j});
}
}
}
if(dist[n]==0x3f3f3f3f)return -1;
return dist[n];
}
int main(){
memset(h,-1,sizeof h);
cin>>n>>m;
while(m--){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
cout<<dijkstra()<<endl;
return 0;
}
Bellman-Ford
该算法适合解决有边数限制的最短路问题
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
int dist[N],backup[N];
int k,n,m;
struct edge{
int a;int b;int w;
}edge[N];
int bellman_ford()
{
memset(dist,0x3f,sizeof dist);
dist[1]=0;
for(int i=1;i<=k;i++)
{
memcpy(backup,dist,sizeof dist);
for(int j=1;j<=m;j++)
{
int a=edge[j].a,b=edge[j].b,w=edge[j].w;
dist[b]=min(dist[b],backup[a]+w);
}
}
return dist[n];
}
int main()
{
cin>>n>>m>>k;
for(int i=1;i<=m;i++)
{
int a,b,c;
cin>>a>>b>>c;
edge[i].a=a,edge[i].b=b,edge[i].w=c;
}
int t=bellman_ford();
if(t>=0x3f3f3f3f/2)puts("impossible");
else cout<<t<<endl;
}
spfa
(边权值存在负数)
spfa求最短路
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 100010;
int h[N], e[N], w[N], ne[N], idx;//邻接表,存储图
int st[N];//标记顶点是不是在队列中
int dist[N];//保存最短路径的值
int q[N], hh, tt = -1;//队列
void add(int a, int b, int c){//图中添加边和边的端点
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}
void spfa(){
q[++tt] = 1;//从1号顶点开始松弛,1号顶点入队
dist[1] = 0;//1号到1号的距离为 0
st[1] = 1;//1号顶点在队列中
while(tt >= hh){//不断进行松弛
int a = q[hh++];//取对头记作a,进行松弛
st[a] = 0;//取完队头后,a不在队列中了
for(int i = h[a]; i != -1; i = ne[i])//遍历所有和a相连的点
{
int b = e[i], c = w[i];//获得和a相连的点和边
if(dist[b] > dist[a] + c){//如果可以距离变得更短,则更新距离
dist[b] = dist[a] + c;//更新距离
if(!st[b]){//如果没在队列中
q[++tt] = b;//入队
st[b] = 1;//打标记
}
}
}
}
}
int main(){
memset(h, -1, sizeof h);//初始化邻接表
memset(dist, 0x3f, sizeof dist);//初始化距离
int n, m;//保存点的数量和边的数量
cin >> n >> m;
for(int i = 0; i < m; i++){//读入每条边和边的端点
int a, b, w;
cin >> a >> b >> w;
add(a, b, w);//加入到邻接表
}
spfa();
if(dist[n] == 0x3f3f3f3f )//如果到n点的距离是无穷,则不能到达
cout << "impossible";
else cout << dist[n];//否则能到达,输出距离
return 0;
}
spfa判断负环
#include <bits/stdc++.h>
using namespace std;
typedef long long i64;
const int INF = 0x3f3f3f3f;
int n, m;
bool spfa_better() {
using edge = std::pair<int ,int>;
cin >> n >> m;
vector<vector<edge>> G(n);
while(m --) {
int a, b, z;
cin >> a >> b >> z;
G[a - 1].emplace_back(b - 1, z);
}
vector<int> dist(n, 0);
vector<int> pre(n, -1);
vector<bool> inQueue(n, true);
queue<int> queue;
for (int i = 0; i < n; i++) {
queue.emplace(i);
}
int idx = 0; // 计数器
auto detectCycle = [&]() {
vector<int> vec;
vector<bool> inStack(n, false);
vector<bool> vis(n, false);
for (int i = 0; i < n; i++) {
if (!vis[i]) {
for (int j = i; j != -1; j = pre[j]) {
if (!vis[j]) {
vis[j] = true;
vec.push_back(j);
inStack[j] = true;
} else {
if (inStack[j]) return true;
break;
}
}
for (int j : vec) inStack[j] = false;
vec.clear();
}
}
return false;
};
while(!queue.empty()) {
int u = queue.front();
queue.pop();
inQueue[u] = false;
for (auto [v, w] : G[u]) {
if (dist[u] + w < dist[v]) {
pre[v] = u;
dist[v] = dist[u] + w;
if (++idx == n) {
idx = 0;
if (detectCycle()) return true;
}
if (!inQueue[v]) {
queue.push(v);
inQueue[v] = true;
}
}
}
}
if (detectCycle()) return true;
return false;
}
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
if (spfa_better()) std::cout << "Yes" <<std::endl;
else cout << "No" << std::endl;
return 0;
}
Floyd
#include <iostream>
using namespace std;
const int N = 210, M = 2e+10, INF = 1e9;
int n, m, k, x, y, z;
int d[N][N];
void floyd() {
for(int k = 1; k <= n; k++)
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
int main() {
cin >> n >> m >> k;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
if(i == j) d[i][j] = 0;
else d[i][j] = INF;
while(m--) {
cin >> x >> y >> z;
d[x][y] = min(d[x][y], z);
//注意保存最小的边
}
floyd();
while(k--) {
cin >> x >> y;
if(d[x][y] > INF/2) puts("impossible");
//由于有负权边存在所以约大过INF/2也很合理
else cout << d[x][y] << endl;
}
return 0;
}
Prim(求最小生成树)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 510;
int g[N][N];//存储图
int dt[N];//存储各个节点到生成树的距离
int st[N];//节点是否被加入到生成树中
int pre[N];//节点的前去节点
int n, m;//n 个节点,m 条边
void prim()
{
memset(dt,0x3f, sizeof(dt));//初始化距离数组为一个很大的数(10亿左右)
int res= 0;
dt[1] = 0;//从 1 号节点开始生成
for(int i = 0; i < n; i++)//每次循环选出一个点加入到生成树
{
int t = -1;
for(int j = 1; j <= n; j++)//每个节点一次判断
{
if(!st[j] && (t == -1 || dt[j] < dt[t]))//如果没有在树中,且到树的距离最短,则选择该点
t = j;
}
//2022.6.1 发现测试用例加强后,需要判断孤立点了
//如果孤立点,直返输出不能,然后退出
if(dt[t] == 0x3f3f3f3f) {
cout << "impossible";
return;
}
st[t] = 1;// 选择该点
res += dt[t];
for(int i = 1; i <= n; i++)//更新生成树外的点到生成树的距离
{
if(dt[i] > g[t][i] && !st[i])//从 t 到节点 i 的距离小于原来距离,则更新。
{
dt[i] = g[t][i];//更新距离
pre[i] = t;//从 t 到 i 的距离更短,i 的前驱变为 t.
}
}
}
cout << res;
}
void getPath()//输出各个边
{
for(int i = n; i > 1; i--)//n 个节点,所以有 n-1 条边。
{
cout << i <<" " << pre[i] << " "<< endl;// i 是节点编号,pre[i] 是 i 节点的前驱节点。他们构成一条边。
}
}
int main()
{
memset(g, 0x3f, sizeof(g));//各个点之间的距离初始化成很大的数
cin >> n >> m;//输入节点数和边数
while(m --)
{
int a, b, w;
cin >> a >> b >> w;//输出边的两个顶点和权重
g[a][b] = g[b][a] = min(g[a][b],w);//存储权重
}
prim();//求最下生成树
//getPath();//输出路径
return 0;
}
Kruskal
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int p[N];//保存并查集
struct E{
int a;
int b;
int w;
bool operator < (const E& rhs){//通过边长进行排序
return this->w < rhs.w;
}
}edg[N * 2];
int res = 0;
int n, m;
int cnt = 0;
int find(int a){//并查集找祖宗
if(p[a] != a) p[a] = find(p[a]);
return p[a];
}
void klskr(){
for(int i = 1; i <= m; i++)//依次尝试加入每条边
{
int pa = find(edg[i].a);// a 点所在的集合
int pb = find(edg[i].b);// b 点所在的集合
if(pa != pb){//如果 a b 不在一个集合中
res += edg[i].w;//a b 之间这条边要
p[pa] = pb;// 合并a b
cnt ++; // 保留的边数量+1
}
}
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++) p[i] = i;//初始化并查集
for(int i = 1; i <= m; i++){//读入每条边
int a, b , c;
cin >> a >> b >>c;
edg[i] = {a, b, c};
}
sort(edg + 1, edg + m + 1);//按边长排序
klskr();
//如果保留的边小于点数-1,则不能连通
if(cnt < n - 1) {
cout<< "impossible";
return 0;
}
cout << res;
return 0;
}
题目: 电动车
#include <iostream>
#include<algorithm>
using namespace std;
const int N=200005;
int p[N];
int n,m,ans=0;
void init(){
for(int i=1;i<=n;i++)p[i]=i;
}
int find(int x){
if(p[x]!=x)p[x]=find(p[x]);
return p[x];
}
struct node{
int a,b,w;
}edg[N*2];
bool compare(node x,node y){
return x.w<y.w;
}
void krual(){
for(int i=1;i<=m;i++){
int xx=find(edg[i].a);
int yy=find(edg[i].b);
if(xx!=yy){p[xx]=yy;ans++;
if(ans==n-1){
cout<<edg[i].w<<endl;
return;
}
}
}
cout<<"-1"<<endl;
}
int main()
{
cin>>n>>m;
init();
for(int i=1;i<=m;i++){
scanf("%d%d%d",&edg[i].a,&edg[i].b,&edg[i].w);
}
sort(edg+1,edg+m+1,compare);
krual();
return 0;
}
染色法判定二分图
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010 * 2;
int e[N], ne[N], idx;//邻接表存储图
int h[N];
int color[N];//保存各个点的颜色,0 未染色,1 是红色,2 是黑色
int n, m;//点和边
void add(int a, int b)//邻接表插入点和边
{
e[idx] = b, ne[idx]= h[a], h[a] = idx++;
}
bool dfs(int u, int c)//深度优先遍历
{
color[u] = c;//u的点成 c 染色
//遍历和 u 相邻的点
for(int i = h[u]; i!= -1; i = ne[i])
{
int b = e[i];
if(!color[b])//相邻的点没有颜色,则递归处理这个相邻点
{
if(!dfs(b, 3 - c)) return false;//(3 - 1 = 2, 如果 u 的颜色是2,则和 u 相邻的染成 1)
//(3 - 2 = 1, 如果 u 的颜色是1,则和 u 相邻的染成 2)
}
else if(color[b] && color[b] != 3 - c)//如果已经染色,判断颜色是否为 3 - c
{
return false;//如果不是,说明冲突,返回
}
}
return true;
}
int main()
{
memset(h, -1, sizeof h);//初始化邻接表
cin >> n >> m;
for(int i = 1; i <= m; i++)//读入边
{
int a, b;
cin >> a >> b;
add(a, b), add(b, a);
}
for(int i = 1; i <= n; i++)//遍历点
{
if(!color[i])//如果没染色
{
if(!dfs(i, 1))//染色该点,并递归处理和它相邻的点
{
cout << "No" << endl;//出现矛盾,输出NO
return 0;
}
}
}
cout << "Yes" << endl;//全部染色完成,没有矛盾,输出YES
return 0;
}
匈牙利算法
#include<iostream>
#include<cstring>
using namespace std;
const int N = 510 , M = 100010;
int n1,n2,m;
int h[N],ne[M],e[M],idx;
bool st[N];
int match[N];
void add(int a , int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void init()
{
memset(h,-1,sizeof h);
}
int find(int x)
{
//遍历自己喜欢的女孩
for(int i = h[x] ; i != -1 ;i = ne[i])
{
int j = e[i];
if(!st[j])//如果在这一轮模拟匹配中,这个女孩尚未被预定
{
st[j] = true;//那x就预定这个女孩了
//如果女孩j没有男朋友,或者她原来的男朋友能够预定其它喜欢的女孩。配对成功
if(!match[j]||find(match[j]))
{
match[j] = x;
return true;
}
}
}
//自己中意的全部都被预定了。配对失败。
return false;
}
int main()
{
init();
cin>>n1>>n2>>m;
while(m--)
{
int a,b;
cin>>a>>b;
add(a,b);
}
int res = 0;
for(int i = 1; i <= n1 ;i ++)
{
//因为每次模拟匹配的预定情况都是不一样的所以每轮模拟都要初始化
memset(st,false,sizeof st);
if(find(i))
res++;
}
cout<<res<<endl;
}
数学知识
质数
试除法判断质数
bool is_prime(int n){
if(n < 2) return false;
for(int i = 2;i <= n / i;i ++){ //优化内容
if(n % i == 0){
return false;
}
}
return true;
}
分解质因数
#include<iostream>
using namespace std;
int main(){
int n;
int x;
cin>>n;
while(n--){
cin>>x;
int s;
for(int i=2;i<=x/i;i++){
s=0;
if(x%i==0){while(x%i==0){
x/=i;
s++;
}
cout<<i<<" "<<s<<endl;
}
}
if(x>1)cout<<x<<" "<<1<<endl;
cout<<endl;
}
}
筛质数
普通筛法(O(nlogn))
void get_primes2(){
for(int i=2;i<=n;i++){
if(!st[i]) primes[cnt++]=i;//把素数存起来
for(int j=i;j<=n;j+=i){//不管是合数还是质数,都用来筛掉后面它的倍数
st[j]=true;
}
}
}
埃氏筛法(O(nloglogn))
void get_primes1(){
for(int i=2;i<=n;i++){
if(!st[i]){
primes[cnt++]=i;
for(int j=i;j<=n;j+=i) st[j]=true;//可以用质数就把所有的合数都筛掉;
}
}
}
线性筛法(O(n))
void get_primes(){
//外层从2~n迭代,因为这毕竟算的是1~n中质数的个数,而不是某个数是不是质数的判定
for(int i=2;i<=n;i++){
if(!st[i]) primes[cnt++]=i;
for(int j=0;primes[j]<=n/i;j++){//primes[j]<=n/i:变形一下得到——primes[j]*i<=n,把大于n的合数都筛了就
//没啥意义了
st[primes[j]*i]=true;//用最小质因子去筛合数
//1)当i%primes[j]!=0时,说明此时遍历到的primes[j]不是i的质因子,那么只可能是此时的primes[j]<i的
//最小质因子,所以primes[j]*i的最小质因子就是primes[j];
//2)当有i%primes[j]==0时,说明i的最小质因子是primes[j],因此primes[j]*i的最小质因子也就应该是
//prime[j],之后接着用st[primes[j+1]*i]=true去筛合数时,就不是用最小质因子去更新了,因为i有最小
//质因子primes[j]<primes[j+1],此时的primes[j+1]不是primes[j+1]*i的最小质因子,此时就应该
//退出循环,避免之后重复进行筛选。
if(i%primes[j]==0) break;
}
}
}
约数
试除法求约数
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main(){
int n;
cin>>n;
while(n--){
int x;
cin>>x;
vector<int>a;
for(int i=1;i<=x/i;i++){
if(x%i==0){a.push_back(i);
if(i!=x/i)a.push_back(x/i);
}
}
sort(a.begin(),a.end());
for(int i=0;i<a.size();i++)
cout<<a[i]<<" ";
cout<<endl;
}
}
最大公约数
可以使用c++自带的函数__gcd
所需要的头文件#inlcude<algorithm>
#include<iostream>
using namespace std;
int gcd(int a, int b) {
int fenzi = a, fenmu = b;
while (fenmu!= 0) {
int temp =fenzi%fenmu;
fenzi=fenmu;
fenmu= temp;
}
return fenzi;
}
int main() {
int n;
cin>>n;
int a, b;
for (int i = 0; i < n; i++) {
scanf("%d %d", &a, &b);
cout << gcd(a, b) << endl;
}
}
int gcd(int a, int b)//求最大公约数
{
return b ? gcd(b, a % b) : a;
}
欧拉函数
快速幂
long long qmi(long long a,int b,int p){
long long res=1;
while(b){
if(b&1)res=res*a%p;
b=b>>1;
a=a*a%p;
}
return res;
}
扩展欧几里得算法
中国剩余定理
高斯消元
求组合数
容斥原理
博弈论
动态规划
背包问题
01背包问题
for(int i = 1; i <= n; i++)
{
for(int j = m; j >= v[i]; j--)
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
完全背包问题
for(int i = 1 ; i<=n ;i++)
for(int j = v[i] ; j<=m ;j++)//注意了,这里的j是从小到大枚举,和01背包不一样
{
f[j] = max(f[j],f[j-v[i]]+w[i]);
}
多重背包问题
#include<iostream>
using namespace std;
const int N=10010;
int f[N];
int main(){
int n,m;
cin>>n>>m;
for(int i=0;i<n;i++){
int vi,wi,si;
cin>>vi>>wi>>si;
while(si--){
for(int j=m;j>=vi;j--)
f[j]=max(f[j],f[j-vi]+wi);
}
}
cout<<f[m];
return 0;
}
多重背包问题二(数的范围更大)
#include<iostream>
using namespace std;
const int N = 12010, M = 2010;
int n, m;
int v[N], w[N]; //逐一枚举最大是N*logS
int f[M]; // 体积<M
int main()
{
cin >> n >> m;
int cnt = 0; //分组的组别
for(int i = 1;i <= n;i ++)
{
int a,b,s;
cin >> a >> b >> s;
int k = 1; // 组别里面的个数
while(k<=s)
{
cnt ++ ; //组别先增加
v[cnt] = a * k ; //整体体积
w[cnt] = b * k; // 整体价值
s -= k; // s要减小
k *= 2; // 组别里的个数增加
}
//剩余的一组
if(s>0)
{
cnt ++ ;
v[cnt] = a*s;
w[cnt] = b*s;
}
}
n = cnt ; //枚举次数正式由个数变成组别数
//01背包一维优化
for(int i = 1;i <= n ;i ++)
for(int j = m ;j >= v[i];j --)
f[j] = max(f[j],f[j-v[i]] + w[i]);
cout << f[m] << endl;
return 0;
}
分组背包问题
#include<iostream>
#include<algorithm>
using namespace std;
const int N=110;
int v[N][N],w[N][N],s[N];
int f[N];
int n,m;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>s[i];
for(int j=0;j<s[i];j++)
cin>>v[i][j]>>w[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(v[i][k]<=j)f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
cout<<f[m];
}
线性DP
数字三角形
#include<iostream>
using namespace std;
const int N=510;
int f[N][N];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
scanf("%d",&f[i][j]);
for(int i=n;i>=1;i--)
for(int j=i;j>=1;j--)
f[i][j]=max(f[i+1][j],f[i+1][j+1])+f[i][j];
cout<<f[1][1];
}
#include<iostream>
using namespace std;
const int N=105;
int dp[N][N];
int main(){
int t;cin>>t;
while(t--){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
int x;
scanf("%d",&x);
dp[i][j]=max(dp[i][j-1],dp[i-1][j])+x;
}
}
cout<<dp[n][m]<<endl;
}
return 0;
}
#include<iostream>
#include<cmath>
#include<cstring>
using namespace std;
int f[110][110];
int main(){
int n;
cin>>n;
int x;
memset(f,0x3f,sizeof f);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&x);
if(i==1&&j==1)f[i][j]=x;
else{
f[i][j]=min(f[i-1][j],f[i][j-1])+x;
}
}
}
cout<<f[n][n]<<endl;
return 0;
}
最长上升子序列
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1010;
int g[N],f[N];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%d",&g[i]);
f[i]=1;
for(int j=1;j<i;j++)
if(g[j]<g[i])
f[i]=max(f[i],f[j]+1);
}
int res=0;
for(int i=1;i<=n;i++)res=max(res,f[i]);
cout<<res;
}
最长上升子序列二
(使用了二分)
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int a[N],f[N];
int cnt;
int find(int x){
int l=1,r=cnt;
while(l<r){
int mid=l+r>>1;
if(f[mid]>=x)r=mid;
else l=mid+1;
}
return l;
}
int main(){
int n;
scanf("%d", &n);
for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
f[++cnt] = a[1];
for(int i = 2; i <= n; i ++)
if(a[i] > f[cnt]) f[ ++ cnt] = a[i];
else {
int tmp = find(a[i]);
f[tmp] = a[i];
}
printf("%d\n", cnt);
return 0;
}
最大上升子序列和
#include<iostream>
#include<cmath>
using namespace std;
int f[1010],a[1010];
int main(){
int n;
cin>>n;
int sum=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
f[i]=a[i];
for(int j=1;j<i;j++)
if(a[j]<a[i])f[i]=max(f[i],a[i]+f[j]);
sum=max(sum,f[i]);
}
cout<<sum<<endl;
}
最长公共子序列
#include<iostream>
#include<algorithm>
using namespace std;
char a[1010],b[1005];
int f[1010][1010];
int main(){
int n,m;
cin>>n>>m;
scanf("%s%s",a+1,b+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(a[i]==b[j])
f[i][j]=f[i-1][j-1]+1;
else
f[i][j]=max(f[i-1][j],f[i][j-1]);
cout<<f[n][m];
}
最短编辑距离
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int m, n;
char a[N],b[N];
int dp[N][N];
int main()
{
scanf("%d%s", &m, a + 1); // a + 1 这里的小技巧
scanf("%d%s", &n, b + 1);
for(int i = 0; i <= m; i++) dp[i][0] = i; //全删除
for(int i = 0; i <= n; i++) dp[0][i] = i; //全插入
for(int i = 1; i <=m; i++)
for(int j = 1; j <=n; j++)
{
dp[i][j] = min(dp[i][j-1], dp[i-1][j]) + 1;
dp[i][j] = min(dp[i][j], dp[i-1][j-1] + (a[i] != b[j])); // 注意这里 i的原因 是 scanf("%d%s", &m, a + 1);
}
printf("%d\n",dp[m][n]);
return 0;
}
区间DP
for (int len = 1; len <= n; len++) { // 区间长度
for (int i = 1; i + len - 1 <= n; i++) { // 枚举起点
int j = i + len - 1; // 区间终点
if (len == 1) {
dp[i][j] = 初始值
continue;
}
for (int k = i; k < j; k++) { // 枚举分割点,构造状态转移方程
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + w[i][j]);
}
}
}
石子合并
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
const int N=310;
int a[N],s[N];
int f[N][N];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
s[i]=s[i-1]+a[i];
}
for(int i=n;i>=1;i--){
for(int j=i;j<=n;j++){
if (j == i) {
f[i][j] = 0;
continue;
}
f[i][j] = 1e9;
for (int k = i; k < j; k++) {
f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j] + s[j] - s[i - 1]);
}
}
}
cout<<f[1][n];
}
计数类DP
整数划分
#include<iostream>
using namespace std;
const int N=1010,mod=1e9+7;
int f[N];
int main(){
int n;
cin>>n;
f[0]=1;
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
f[j]=(f[j]+f[j-i])%mod;
cout<<f[n];
}
数位统计DP
# include <iostream>
# include <cmath>
using namespace std;
int dgt(int n) // 计算整数n有多少位
{
int res = 0;
while (n) ++ res, n /= 10;
return res;
}
int cnt(int n, int i) // 计算从1到n的整数中数字i出现多少次
{
int res = 0, d = dgt(n);
for (int j = 1; j <= d; ++ j) // 从右到左第j位上数字i出现多少次
{
// l和r是第j位左边和右边的整数 (视频中的abc和efg); dj是第j位的数字
int p = pow(10, j - 1), l = n / p / 10, r = n % p, dj = n / p % 10;
// 计算第j位左边的整数小于l (视频中xxx = 000 ~ abc - 1)的情况
if (i) res += l * p;
if (!i && l) res += (l - 1) * p; // 如果i = 0, 左边高位不能全为0(视频中xxx = 001 ~ abc - 1)
// 计算第j位左边的整数等于l (视频中xxx = abc)的情况
if ( (dj > i) && (i || l) ) res += p;
if ( (dj == i) && (i || l) ) res += r + 1;
}
return res;
}
int main()
{
int a, b;
while (cin >> a >> b , a)
{
if (a > b) swap(a, b);
for (int i = 0; i <= 9; ++ i) cout << cnt(b, i) - cnt(a - 1, i) << ' ';
cout << endl;
}
return 0;
}
状态压缩DP
树形DP
记忆化搜索
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 310;
int n,m; //网格滑雪场的行和列
int f[N][N]; //状态转移式
int h[N][N]; //网格滑雪场
int dx[4] = {-1,0,1,0};
int dy[4] = {0,1,0,-1};
int dp(int x,int y){
int &v = f[x][y]; //Y总说的小技巧,等于把f[x][y]简化成了v,如果v发生变化,f[x][y]也会随之变化
if(v != -1) return v; //如果已经计算过了,就可以直接返回答案
v = 1; //注意v要先赋值为1哦~
for(int i = 0;i < 4;i ++){ //四个方向
int xx = x + dx[i];
int yy = y + dy[i];
if(xx >= 1 && xx <= n && yy >= 1 && yy <= m && h[x][y] > h[xx][yy]){ //判断这点是否能走
v = max(v,dp(xx,yy) + 1); //更新
}
}
return v; //别忘了返回v啊(被坑了
}
int main(){
cin>>n>>m; //输入滑雪场行和列
for(int i = 1;i <= n;i ++){
for(int j = 1;j <= m;j ++){
cin>>h[i][j]; //读入滑雪场数据
}
}
memset(f,-1,sizeof f);
int res = 0; //最后答案
for(int i = 1;i <= n;i ++){
for(int j = 1;j <= m;j ++){
//因为这个人可以在任意一点开始滑,所以要遍历一遍滑雪场
res = max(res,dp(i,j)); //更新答案
}
}
cout<<res<<endl;
return 0;
}
贪心
区间问题
区间选点
#include<iostream>
#include<algorithm>
using namespace std;
struct point{
int l,r;
}a[100010];
bool compare(point x,point y){
if(x.r==y.r)return x.l<y.l;
return x.r<y.r;
}
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i].l>>a[i].r;
}
sort(a,a+n,compare);
int ans=1;
int t=a[0].r;
int i=1;
while(i<n){
if(t<a[i].l||t>a[i].r){t=a[i].r;ans++;}
i++;
}
cout<<ans<<endl;
return 0;
}
最大不相交区间数
#include<iostream>
#include<algorithm>
using namespace std;
struct point{
int l,r;
}a[100010];
bool compare(point x,point y){
if(x.r==y.r)return x.l<y.l;
return x.r<y.r;
}
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++){
cin>>a[i].l>>a[i].r;
}
sort(a,a+n,compare);
int ans=1;
int t=a[0].r;
int i=1;
while(i<n){
if(t<a[i].l||t>a[i].r){t=a[i].r;ans++;}
i++;
}
cout<<ans<<endl;
return 0;
}
区间分组
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100100;
int n;
int b[2 * N], idx;
int main()
{
scanf ("%d", &n);
for(int i = 0; i < n; i ++)
{
int l, r;
scanf("%d %d", &l, &r);
b[idx ++] = l * 2;//标记左端点为偶数。
b[idx ++] = r * 2 + 1;// 标记右端点为奇数。
}
sort(b, b + idx);
int res = 1, t = 0;
for(int i = 0; i < idx; i ++)
{
if(b[i] % 2 == 0) t ++;
else t --;
res = max(res, t);
}
printf ("%d\n", res);
return 0;
}
区间覆盖
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
struct Range
{
int l, r;
bool operator< (const Range &W)const
{
return l < W.l;
}
}range[N];
int main()
{
int st, ed;
scanf("%d%d", &st, &ed);
scanf("%d", &n);
for (int i = 0; i < n; i ++ )
{
int l, r;
scanf("%d%d", &l, &r);
range[i] = {l, r};
}
sort(range, range + n);
int res = 0;
bool success = false;
for (int i = 0; i < n; i ++ )
{
int j = i, r = -2e9;
while (j < n && range[j].l <= st)
{
r = max(r, range[j].r);
j ++ ;
}
if (r < st)
{
res = -1;
break;
}
res ++ ;
if (r >= ed)
{
success = true;
break;
}
st = r;
i = j - 1;
}
if (!success) res = -1;
printf("%d\n", res);
return 0;
}
哈夫曼树
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
int main(){
int n;
cin>>n;
priority_queue<int,vector<int>,greater<int>>heap;//定义小根堆
while(n--){
int x;
cin>>x;
heap.push(x);
}
int res=0;
while(heap.size()>1){
int a=heap.top();heap.pop();
int b=heap.top();heap.pop();
res+=a+b;
heap.push(a+b);
}
cout<<res<<endl;
}
排序不等式
排队打水
#include<iostream>
#include<algorithm>
using namespace std;
int a[100010];
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++)cin>>a[i];
sort(a,a+n);
long long ans=0,t=a[0];
for(int i=1;i<n;i++){
ans+=t;
t+=a[i];
}
cout<<ans;
return 0;
}
绝对不等式
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
int a[100010],sum;
int main(){
int n;
cin>>n;
for(int i=0;i<n;i++)cin>>a[i];
sort(a,a+n);
int k=a[n/2];
for(int i=0;i<n;i++)sum+=abs(a[i]-k);
cout<<sum;
return 0;
}
推公式
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
const int N = 5e4 + 5;
PII a[N];
int main()
{
int n;
cin >> n;
for(int i = 0; i < n; i ++ )
{
int x, y;
scanf("%d %d", &x, &y);
a[i].first = x + y;
a[i].second = y;
}
sort(a, a + n);
ll res = -1e18, sum = 0;
for(int i = 0; i < n; i ++ )
{
sum -= a[i].second;
res = max(res, sum);
sum += a[i].first;
}
cout << res << endl;
return 0;
}