1. 动态规划(43/68)

1.1 数字三角形模型(4/4)

1.1.1 AcWing 1015. 摘花生

结论: f[i][j]=max⁡(f[i−1][j],f[i][j−1])+w[i][j]f[i][j]=\max(f[i-1][j],f[i][j-1])+w[i][j]f[i][j]=max(f[i−1][j],f[i][j−1])+w[i][j]

思路:

  1. 状态表示
    集合:从 (1,1)(1,1)(1,1) 走到 (i,j)(i,j)(i,j) 的所有路线
    属性:max⁡\maxmax
  2. 状态转移
    从 (i,j)(i,j)(i,j) 上边走来:f[i][j]=f[i−1][j]+w[i][j]f[i][j]=f[i-1][j]+w[i][j]f[i][j]=f[i−1][j]+w[i][j]
    从 (i,j)(i,j)(i,j) 左边走来:f[i][j]=f[i][j−1]+w[i][j]f[i][j]=f[i][j-1]+w[i][j]f[i][j]=f[i][j−1]+w[i][j]
  3. 滚动数组
    由于 f[i][j]f[i][j]f[i][j] 只需用到当前层和上一层,所以结论可以转化为:f[j]=max⁡(f[j],f[j−1])+w[i][j]f[j]=\max(f[j],f[j-1])+w[i][j]f[j]=max(f[j],f[j−1])+w[i][j]

代码: O(n2)O(n^2)O(n2)

#include<bits/stdc++.h>using namespace std;const int N = 110;int n,m;
int w[N][N];
int f[N];int main()
{int T;cin>>T;while(T--){cin>>n>>m;memset(f,0,sizeof f); //多组数据初始化for(int i=1;i<=n;i++)for(int j=1;j<=m;j++) cin>>w[i][j];for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)f[j]=max(f[j],f[j-1])+w[i][j]; //结论cout<<f[m]<<endl;}return 0;
}

1.1.2 AcWing 1018. 最低通行费

结论: f[i][j]=min⁡(f[i−1][j],f[i][j−1])+w[i][j]f[i][j]=\min(f[i-1][j],f[i][j-1])+w[i][j]f[i][j]=min(f[i−1][j],f[i][j−1])+w[i][j]

思路:

  1. 状态表示
    集合:从 (1,1)(1,1)(1,1) 走到 (i,j)(i,j)(i,j) 的所有路线
    属性:min⁡\minmin
  2. 状态转移
    从 (i,j)(i,j)(i,j) 上边走来:f[i][j]=f[i−1][j]+w[i][j]f[i][j]=f[i-1][j]+w[i][j]f[i][j]=f[i−1][j]+w[i][j]
    从 (i,j)(i,j)(i,j) 左边走来:f[i][j]=f[i][j−1]+w[i][j]f[i][j]=f[i][j-1]+w[i][j]f[i][j]=f[i][j−1]+w[i][j]
  3. 滚动数组
    由于 f[i][j]f[i][j]f[i][j] 只需用到当前层和上一层,所以结论可以转化为:f[j]=min⁡(f[j],f[j−1])+w[i][j]f[j]=\min(f[j],f[j-1])+w[i][j]f[j]=min(f[j],f[j−1])+w[i][j]

代码: O(n2)O(n^2)O(n2)

#include<bits/stdc++.h>using namespace std;const int N = 110;int n;
int w[N][N];
int f[N];int main()
{cin>>n;for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)cin>>w[i][j];memset(f,0x3f,sizeof f); //多组数据初始化f[1]=w[1][1];for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(i!=1||j!=1)f[j]=min(f[j],f[j-1])+w[i][j]; //结论cout<<f[n]<<endl;return 0;
}

1.1.3 AcWing 1027. 方格取数

推导:

111 号和 222 号走的步数相同:k=i1+j1=i2+j2k=i1+j1=i2+j2k=i1+j1=i2+j2
公式变换:j1=k−i1,j2=k−i2j1=k-i1,j2=k-i2j1=k−i1,j2=k−i2
则 f[k][i1][j1][i2][j2]=f[k][i1][k−i1][i2][k−i2]⇒f[k][i1][i2]f[k][i1][j1][i2][j2]=f[k][i1][k-i1][i2][k-i2]\Rightarrow f[k][i1][i2]f[k][i1][j1][i2][j2]=f[k][i1][k−i1][i2][k−i2]⇒f[k][i1][i2]

思路:

  1. 状态表示
    集合:从 (1,1),(1,1)(1,1),(1,1)(1,1),(1,1) 走到 (i1,j1),(i2,j2)(i1,j1),(i2,j2)(i1,j1),(i2,j2) 的所有路线
    属性:max⁡\maxmax
  2. 状态转移
    111 号从 (i1,j1)(i1,j1)(i1,j1) 上方走来,222 号从 (i2,j2)(i2,j2)(i2,j2) 上方走来:f[k][i1][i2]=f[k−1][i1−1][i2−1]+w[i1][j1]f[k][i1][i2]=f[k-1][i1-1][i2-1]+w[i1][j1]f[k][i1][i2]=f[k−1][i1−1][i2−1]+w[i1][j1]
    111 号从 (i1,j1)(i1,j1)(i1,j1) 上方走来,222 号从 (i2,j2)(i2,j2)(i2,j2) 左方走来:f[k][i1][i2]=f[k−1][i1−1][i2]+w[i1][j1]f[k][i1][i2]=f[k-1][i1-1][i2]+w[i1][j1]f[k][i1][i2]=f[k−1][i1−1][i2]+w[i1][j1]
    111 号从 (i1,j1)(i1,j1)(i1,j1) 左方走来,222 号从 (i2,j2)(i2,j2)(i2,j2) 上方走来:f[k][i1][i2]=f[k−1][i1][i2−1]+w[i1][j1]f[k][i1][i2]=f[k-1][i1][i2-1]+w[i1][j1]f[k][i1][i2]=f[k−1][i1][i2−1]+w[i1][j1]
    111 号从 (i1,j1)(i1,j1)(i1,j1) 左方走来,222 号从 (i2,j2)(i2,j2)(i2,j2) 左方走来:f[k][i1][i2]=f[k−1][i1][i2]+w[i1][j1]f[k][i1][i2]=f[k-1][i1][i2]+w[i1][j1]f[k][i1][i2]=f[k−1][i1][i2]+w[i1][j1]
    若 111 号和 222 号走的不是同一个点:f[k][i1][i2]+=w[i2][j2]f[k][i1][i2]+=w[i2][j2]f[k][i1][i2]+=w[i2][j2]

代码:

#include<bits/stdc++.h>using namespace std;const int N = 15;int n;
int w[N][N];
int f[N*2][N][N];int main()
{cin>>n;int a,b,c;while(cin>>a>>b>>c,a||b||c) w[a][b]=c;for(int k=1;k<=n*2;k++) // k = i1 + j1 = i2 + j2for(int i1=1;i1<=n;i1++)for(int i2=1;i2<=n;i2++){int j1=k-i1,j2=k-i2;if(j1>=1&&j1<=n&&j2>=1&&j2<=n) // 判断该点是否合法{// 1 号和 2 号都往下走f[k][i1][i2]=max(f[k][i1][i2],f[k-1][i1-1][i2-1]+w[i1][j1]);// 1 号往下走,2 号往右走f[k][i1][i2]=max(f[k][i1][i2],f[k-1][i1-1][i2]+w[i1][j1]);// 1 号往右走,2 号往下走f[k][i1][i2]=max(f[k][i1][i2],f[k-1][i1][i2-1]+w[i1][j1]);// 1 号和 2 号都往右走f[k][i1][i2]=max(f[k][i1][i2],f[k-1][i1][i2]+w[i1][j1]);// 1 号和 2 号走的不是同一个点if(i1!=i2) f[k][i1][i2]+=w[i2][j2];}}cout<<f[n*2][n][n]<<endl; //从 (1,1),(1,1) 走到 (n,n),(n,n) 的最大数字和return 0;
}

1.1.4 AcWing 275. 传纸条

代码:

#include<bits/stdc++.h>using namespace std;const int N = 55;int n,m;
int w[N][N];
int f[N*2][N][N];int main()
{cin>>n>>m;for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>w[i][j];for(int k=1;k<=n+m;k++)for(int i1=1;i1<=n;i1++)for(int i2=1;i2<=n;i2++){int j1=k-i1,j2=k-i2;//必须合法并且不能走到同一个点上(起点和终点除外)if(j1>=1&&j1<=m&&j2>=1&&j2<=m&&(i1!=i2||k==2||k==n+m)){int t=w[i1][j1]+w[i2][j2];// 1 号和 2 号都往下走f[k][i1][i2]=max(f[k][i1][i2],f[k-1][i1-1][i2-1]+t);// 1 号往下走,2 号往右走f[k][i1][i2]=max(f[k][i1][i2],f[k-1][i1-1][i2]+t);// 1 号往右走,2 号往下走f[k][i1][i2]=max(f[k][i1][i2],f[k-1][i1][i2-1]+t);// 1 号和 2 号都往右走f[k][i1][i2]=max(f[k][i1][i2],f[k-1][i1][i2]+t);}}cout<<f[n+m][n][n]<<endl;return 0;
}

1.2 最长上升子序列模型(7/8)

1.2.1 AcWing 1017. 怪盗基德的滑翔翼

思路:

  1. 状态表示
    集合:所有以 hih_ihi​ 结尾的上升子序列
    属性:max⁡\maxmax
  2. 状态转移
    fi=max⁡{fj+1},(j<i,hj<hi)f_i=\max\{f_j+1\},(j<i,h_j<h_i)fi​=max{fj​+1},(j<i,hj​<hi​)
  3. 正向和反向各求解一遍 LISLISLIS 问题,并取 fif_ifi​ 的最大值

代码:

#include<bits/stdc++.h>using namespace std;const int N = 110;int n;
int h[N];
int f[N];int main()
{int T;cin>>T;while(T--){cin>>n;for(int i=1;i<=n;i++) cin>>h[i];//正向求解一遍 LIS 问题int res=0;for(int i=1;i<=n;i++){f[i]=1;for(int j=1;j<i;j++)if(h[j]<h[i])f[i]=max(f[i],f[j]+1);res=max(res,f[i]);}//反向求解一遍 LIS 问题for(int i=n;i;i--){f[i]=1;for(int j=n;j>i;j--)if(h[j]<h[i])f[i]=max(f[i],f[j]+1);res=max(res,f[i]);}cout<<res<<endl;}return 0;
}

1.2.2 AcWing 1014. 登山

代码:

#include<bits/stdc++.h>using namespace std;const int N = 1010;int n;
int a[N];
int f[N],g[N];int main()
{cin>>n;for(int i=1;i<=n;i++) cin>>a[i];// 正向求解一遍 LIS 问题for(int i=1;i<=n;i++){f[i]=1;for(int j=1;j<i;j++)if(a[j]<a[i])f[i]=max(f[i],f[j]+1);}// 反向求解一遍 LIS 问题for(int i=n;i;i--){g[i]=1;for(int j=n;j>i;j--)if(a[j]<a[i])g[i]=max(g[i],g[j]+1);}// res = max{f[i] + g[i] - 1}int res=0;for(int i=1;i<=n;i++) res=max(res,f[i]+g[i]-1);cout<<res<<endl;return 0;
}

1.2.3 AcWing 482. 合唱队形

代码:

#include<bits/stdc++.h>using namespace std;const int N = 110;int n;
int a[N];
int f[N],g[N];int main()
{cin>>n;for(int i=1;i<=n;i++) cin>>a[i];for(int i=1;i<=n;i++){f[i]=1;for(int j=1;j<i;j++)if(a[j]<a[i])f[i]=max(f[i],f[j]+1);}for(int i=n;i;i--){g[i]=1;for(int j=n;j>i;j--)if(a[j]<a[i])g[i]=max(g[i],g[j]+1);}int res=0;for(int i=1;i<=n;i++) res=max(res,f[i]+g[i]-1);cout<<n-res<<endl; // 与“登山”的唯一不同之处return 0;
}

1.2.4 AcWing 1012. 友好城市

思路: 按 aia_iai​ 的第一元素进行排序,并将第二元素看成是第一元素的高度,求一遍 LISLISLIS 问题

代码:

#include<bits/stdc++.h>using namespace std;typedef pair<int,int> PII;const int N = 5010;int n;
PII a[N];
int f[N];int main()
{cin>>n;for(int i=1;i<=n;i++) cin>>a[i].first>>a[i].second;//按第一元素进行排序sort(a+1,a+n+1);//对第二元素做一遍 LIS 问题求解int res=0;for(int i=1;i<=n;i++){f[i]=1;for(int j=1;j<i;j++)if(a[j].second<a[i].second)f[i]=max(f[i],f[j]+1);res=max(res,f[i]);}cout<<res<<endl;return 0;
}

1.2.5 AcWing 1016. 最大上升子序列和

思路:

  1. 状态表示
    集合:所有以 aia_iai​ 为结尾的严格上升子序列的和
    属性:max⁡\maxmax
  2. 状态转移
    fi=max⁡{fj+ai},(j<i,aj<ai)f_i=\max\{f_j+a_i\},(j<i,a_j<a_i)fi​=max{fj​+ai​},(j<i,aj​<ai​)

代码:

#include<bits/stdc++.h>using namespace std;const int N = 1010;int n;
int a[N];
int f[N];int main()
{cin>>n;for(int i=1;i<=n;i++) cin>>a[i];int res=0;for(int i=1;i<=n;i++){f[i]=a[i];for(int j=1;j<i;j++)if(a[j]<a[i])f[i]=max(f[i],f[j]+a[i]);res=max(res,f[i]);}cout<<res<<endl;return 0;
}

1.2.6 AcWing 1010. 拦截导弹

结论: 第一问求最长下降子序列,第二问求最长上升子序列

思路:

  1. 如果现有子序列的结尾数都小于当前数,那就创建一个新的序列
  2. 否则,就将当前数放在现有子序列的大于等于当前数的最小结尾数的后面
  3. 因此我们可以巧妙地发现,他们的结尾数呈一个最长上升子序列的形态

Example:

  1. 母序列:389 207 155 300 299 170 158 65
  2. 子序列1:389 207 155 65
  3. 子序列2:300 299 170 158

代码:

#include<bits/stdc++.h>using namespace std;const int N = 1010;int n;
int a[N];
int f[N],g[N];int main()
{while(cin>>a[n]) n++;int res=0,cnt=0;for(int i=0;i<n;i++){f[i]=g[i]=1;for(int j=0;j<i;j++)if(a[j]>=a[i]) f[i]=max(f[i],f[j]+1); //下降else g[i]=max(g[i],g[j]+1); //上升res=max(res,f[i]);cnt=max(cnt,g[i]);}cout<<res<<'\n'<<cnt<<endl;return 0;
}

1.2.7 AcWing 187. 导弹防御系统

思路:

  1. 本题就是问最少需要多少个最长上升子序列和最长下降子序列
  2. up 数组存所有上升子序列的结尾数,down 数组存所有下降子序列的结尾数,ans 全局变量存最少子序列的个数
  3. 直接用 dfs 暴搜枚举当前数放在上升子序列或下降子序列

代码:

#include<bits/stdc++.h>using namespace std;const int N = 55;int n;
int a[N];
int up[N],down[N]; // up 存所有上升子序列的结尾,down 存所有下降子序列的结尾
int ans;void dfs(int u,int su,int sd)
{if(su+sd>=ans) return; //已无法得到更好的最优解//所有数都已放完if(u==n){ans=su+sd;return;}//将当前数放入上升子序列int k=0;while(k<su&&up[k]>=a[u]) k++; // up数组本身是非严格单调递减函数int t=up[k];up[k]=a[u];if(k<su) dfs(u+1,su,sd); //不用开新组else dfs(u+1,su+1,sd); //需要开一个新组up[k]=t; //恢复现场//将当前数放入下降子序列k=0;while(k<sd&&down[k]<=a[u]) k++; // down数组本身是非严格单调递增函数t=down[k];down[k]=a[u];if(k<sd) dfs(u+1,su,sd); //不用开新组else dfs(u+1,su,sd+1); //需要开一个新组down[k]=t; //恢复现场
}int main()
{while(cin>>n,n){for(int i=0;i<n;i++) cin>>a[i];ans=n;dfs(0,0,0);cout<<ans<<endl;}return 0;
}

1.2.8 AcWing 272. 最长公共上升子序列

1.3 背包模型(16/19)

1.3.1 AcWing 423. 采药

结论: f[i,j]=max⁡(f[i−1,j],f[i−1,j−v[i]]+w[i])f[i,j]=\max(f[i-1,j],f[i-1,j-v[i]]+w[i])f[i,j]=max(f[i−1,j],f[i−1,j−v[i]]+w[i])

思路:

  1. 状态表示
    集合:从前 iii 个物品中选,体积不超过 jjj 的价值
    属性:max⁡\maxmax
  2. 状态转移
    不选第 iii 个物品:f[i−1,j]f[i-1,j]f[i−1,j]
    选择第 iii 个物品:f[i−1,j−v[i]]+w[i]f[i-1,j-v[i]]+w[i]f[i−1,j−v[i]]+w[i]
  3. 滚动数组
    由于 f[i][j]f[i][j]f[i][j] 只需用到当前层和上一层,则结论可转化为:f[j]=max⁡(f[j],f[j−v[i]]+w[i])f[j]=\max(f[j],f[j-v[i]]+w[i])f[j]=max(f[j],f[j−v[i]]+w[i])

代码:

#include<bits/stdc++.h>using namespace std;const int N = 1010;int m,n;
int f[N];int main()
{cin>>m>>n;for(int i=0;i<n;i++){int v,w;cin>>v>>w;for(int j=m;j>=v;j--)f[j]=max(f[j],f[j-v]+w);}cout<<f[m]<<endl; //体积不超过 m 的最大价值return 0;
}

1.3.2 AcWing 1024. 装箱问题

思路: 将体积充当为所谓的“价值”

代码:

#include<bits/stdc++.h>using namespace std;const int N = 20010;int n,m;
int f[N];int main()
{cin>>m>>n;for(int i=1;i<=n;i++){int v;cin>>v;for(int j=m;j>=v;j--) f[j]=max(f[j],f[j-v]+v);}cout<<m-f[m]<<endl;return 0;
}

1.3.3 AcWing 1022. 宠物小精灵之收服

结论: f[i,j,k]=max⁡(f[i−1,j,k],f[i−1,j−v1,k−v2]+1)f[i,j,k]=\max(f[i-1,j,k],f[i-1,j-v1,k-v2]+1)f[i,j,k]=max(f[i−1,j,k],f[i−1,j−v1,k−v2]+1)

思路:

  1. 状态表示
    集合:从前 iii 个物品中选,费用111 不超过 jjj,费用222 不超过 kkk 的价值
    属性:max⁡\maxmax
  2. 状态转移
    不选第 iii 个物品:f[i−1,j,k]f[i-1,j,k]f[i−1,j,k]
    选择第 iii 个物品:f[i−1,j−v1,k−v2]+1f[i-1,j-v1,k-v2]+1f[i−1,j−v1,k−v2]+1
  3. 滚动数组
    由于 f[i,j,k]f[i,j,k]f[i,j,k] 只需用到当前层和上一层,则结论可转化为:f[j,k]=max⁡(f[j,k],f[j−v1,k−v2]+1)f[j,k]=\max(f[j,k],f[j-v1,k-v2]+1)f[j,k]=max(f[j,k],f[j−v1,k−v2]+1)

代码:

#include<bits/stdc++.h>using namespace std;const int N = 1010, M = 510;int V1,V2,n;
int f[N][M]; //从前 i 个物品中选,费用1 不超过 j,费用2 不超过 k 的最大价值int main()
{cin>>V1>>V2>>n;for(int i=1;i<=n;i++){int v1,v2;cin>>v1>>v2;for(int j=V1;j>=v1;j--)for(int k=V2;k>=v2;k--)f[j][k]=max(f[j][k],f[j-v1][k-v2]+1);}//在价值最大的情况下,费用2 最少是多少int k=V2-1;while(k>0&&f[V1][k-1]==f[V1][V2-1]) k--;cout<<f[V1][V2-1]<<' '<<V2-k<<endl;return 0;
}

1.3.4 AcWing 278. 数字组合

结论: f[i,j]=f[i−1,j]+f[i−1,j−v]f[i,j]=f[i-1,j]+f[i-1,j-v]f[i,j]=f[i−1,j]+f[i−1,j−v]

思路:

  1. 状态表示
    集合:从前 iii 个物品中选,体积等于 jjj 的方案
    属性:CountCountCount
  2. 状态转移
    f[i,j]=f[i−1,j]+f[i−1,j−v]f[i,j]=f[i-1,j]+f[i-1,j-v]f[i,j]=f[i−1,j]+f[i−1,j−v]
  3. 滚动数组
    由于 f[i,j]f[i,j]f[i,j] 只需用到当前层和上一层,则结论可转化为:f[j]=f[j]+f[j−v]f[j]=f[j]+f[j-v]f[j]=f[j]+f[j−v]

代码:

#include<bits/stdc++.h>using namespace std;const int N = 10010;int n,m;
int f[N];int main()
{cin>>n>>m;f[0]=1;for(int i=1;i<=n;i++){int v;cin>>v;for(int j=m;j>=v;j--)f[j]+=f[j-v];}cout<<f[m]<<endl;return 0;
}

1.3.5 AcWing 1023. 买书

结论: f[i,j]=f[i−1,j]+f[i,j−v]f[i,j]=f[i-1,j]+f[i,j-v]f[i,j]=f[i−1,j]+f[i,j−v]

推导过程:

因为,f[i,j]=f[i−1,j]+f[i−1,j−v]+f[i−1,j−2v]+...+f[i−1,j−sv]f[i,j]=f[i-1,j]+f[i-1,j-v]+f[i-1,j-2v]+...+f[i-1,j-sv]f[i,j]=f[i−1,j]+f[i−1,j−v]+f[i−1,j−2v]+...+f[i−1,j−sv]
f[i,j−v]=f[i−1,j−v]+f[i−1,j−2v]+...+f[i−1,j−sv]\ \ \ \ \ \ \ \ \ \ f[i,j-v]=\ \ \ \ \ \ \ \ \ \ \ \ \ \ f[i-1,j-v]+f[i-1,j-2v]+...+f[i-1,j-sv]          f[i,j−v]=              f[i−1,j−v]+f[i−1,j−2v]+...+f[i−1,j−sv]
所以,f[i,j]=f[i−1,j]+f[i,j−v]f[i,j]=f[i-1,j]+f[i,j-v]f[i,j]=f[i−1,j]+f[i,j−v]

思路:

  1. 状态表示
    集合:从前 iii 个物品中选,体积等于 jjj 的方案
    属性:CountCountCount
  2. 状态转移
    f[i,j]=f[i−1,j]+f[i,j−v]f[i,j]=f[i-1,j]+f[i,j-v]f[i,j]=f[i−1,j]+f[i,j−v]
  3. 滚动数组
    由于 f[i,j]f[i,j]f[i,j] 只需用到当前层和上一层,则结论可转化为:f[j]=f[j]+f[j−v]f[j]=f[j]+f[j-v]f[j]=f[j]+f[j−v]

代码:

#include<bits/stdc++.h>using namespace std;const int N = 1010;int n;
int f[N];
int v[4]={10,20,50,100};int main()
{cin>>n;f[0]=1;for(int i=0;i<4;i++)for(int j=v[i];j<=n;j++)f[j]+=f[j-v[i]];cout<<f[n]<<endl;return 0;
}

1.3.6 AcWing 1021. 货币系统

结论: f[i,j]=f[i−1,j]+f[i,j−v]f[i,j]=f[i-1,j]+f[i,j-v]f[i,j]=f[i−1,j]+f[i,j−v]

推导过程:

因为,f[i,j]=f[i−1,j]+f[i−1,j−v]+f[i−1,j−2v]+...+f[i−1,j−sv]f[i,j]=f[i-1,j]+f[i-1,j-v]+f[i-1,j-2v]+...+f[i-1,j-sv]f[i,j]=f[i−1,j]+f[i−1,j−v]+f[i−1,j−2v]+...+f[i−1,j−sv]
f[i,j−v]=f[i−1,j−v]+f[i−1,j−2v]+...+f[i−1,j−sv]\ \ \ \ \ \ \ \ \ \ f[i,j-v]=\ \ \ \ \ \ \ \ \ \ \ \ \ \ f[i-1,j-v]+f[i-1,j-2v]+...+f[i-1,j-sv]          f[i,j−v]=              f[i−1,j−v]+f[i−1,j−2v]+...+f[i−1,j−sv]
所以,f[i,j]=f[i−1,j]+f[i,j−v]f[i,j]=f[i-1,j]+f[i,j-v]f[i,j]=f[i−1,j]+f[i,j−v]

思路:

  1. 状态表示
    集合:从前 iii 个物品中选,体积等于 jjj 的方案
    属性:CountCountCount
  2. 状态转移
    f[i,j]=f[i−1,j]+f[i,j−v]f[i,j]=f[i-1,j]+f[i,j-v]f[i,j]=f[i−1,j]+f[i,j−v]
  3. 滚动数组
    由于 f[i,j]f[i,j]f[i,j] 只需用到当前层和上一层,则结论可转化为:f[j]=f[j]+f[j−v]f[j]=f[j]+f[j-v]f[j]=f[j]+f[j−v]

代码:

#include<bits/stdc++.h>using namespace std;typedef long long LL;const int N = 3010;int n,m;
LL f[N];int main()
{cin>>n>>m;f[0]=1;for(int i=0;i<n;i++){int v;cin>>v;for(int j=v;j<=m;j++)f[j]+=f[j-v];}cout<<f[m]<<endl;return 0;
}

1.3.7 AcWing 532. 货币系统

思路:

  1. 易发现 b[i]b[i]b[i] 一定是从 a[1~n]a[1 ~ n]a[1~n] 中无法由其他数字构成的数字
  2. 用完全背包判断该数字是否可有其他数字组成,如果不可以则记录

代码:

#include<bits/stdc++.h>using namespace std;const int N = 110, M = 25010;int n;
int a[N];
int f[M];int main()
{int T;cin>>T;while(T--){cin>>n;for(int i=0;i<n;i++) cin>>a[i];sort(a,a+n); //排序//多组数据初始化memset(f,0,sizeof f);f[0]=1;int res=0;for(int i=0;i<n;i++){if(!f[a[i]]) res++; //该数字无法被其他数字表示for(int j=a[i];j<=a[n-1];j++) f[j]+=f[j-a[i]];}cout<<res<<endl;}return 0;
}

1.3.8 AcWing 6. 多重背包问题 III

1.3.9 AcWing 1019. 庆功会

结论: f[i,j]=max⁡{f[i−1,j],f[i−1,j−k⋅v[i]]+k⋅w[i]}(0⩽k⩽s[i])f[i,j]=\max\{f[i-1,j],f[i-1,j-k\cdot v[i]]+k\cdot w[i]\}\ (0\leqslant k \leqslant s[i])f[i,j]=max{f[i−1,j],f[i−1,j−k⋅v[i]]+k⋅w[i]} (0⩽k⩽s[i])

思路:

  1. 状态表示
    集合:从前 iii 个物品中选,体积不超过 jjj 的价值
    属性:max⁡\maxmax
  2. 状态转移
    第 iii 个物品选 kkk 个(不超过体积的情况下):f[i,j]=max⁡{f[i−1,j],f[i−1,j−k⋅v[i]]+k⋅w[i]}(0⩽k⩽s[i])f[i,j]=\max\{f[i-1,j],f[i-1,j-k\cdot v[i]]+k\cdot w[i]\}\ (0\leqslant k \leqslant s[i])f[i,j]=max{f[i−1,j],f[i−1,j−k⋅v[i]]+k⋅w[i]} (0⩽k⩽s[i])
  3. 滚动数组
    由于 f[i,j]f[i,j]f[i,j] 只需用到当前层和上一层,则结论可转化为:f[j]=max⁡{f[j−k⋅v[i]]+k⋅w[i]}f[j]=\max\{f[j-k\cdot v[i]]+k\cdot w[i]\}f[j]=max{f[j−k⋅v[i]]+k⋅w[i]}

代码:

#include<bits/stdc++.h>using namespace std;const int N = 6010;int n,m;
int f[N];int main()
{cin>>n>>m;for(int i=0;i<n;i++){int v,w,s;cin>>v>>w>>s;for(int j=m;j>=v;j--)for(int k=1;k<=s&&k*v<=j;k++)f[j]=max(f[j],f[j-k*v]+k*w);}cout<<f[m]<<endl;return 0;
}

1.3.10 AcWing 7. 混合背包问题

代码:

#include<bits/stdc++.h>using namespace std;const int N = 1010;int n,m;
int f[N];int main()
{cin>>n>>m;for(int i=0;i<n;i++) {int v,w,s;cin>>v>>w>>s;if(!s) //完全背包{for(int j=v;j<=m;j++)f[j]=max(f[j],f[j-v]+w);}else //多重背包二进制优化{if(s==-1) s=1; // 01背包转换为多重背包for(int k=1;k<=s;k*=2) {for(int j=m;j>=k*v;j--)f[j]=max(f[j],f[j-k*v]+k*w);s-=k;}if(s){for(int j=m;j>=s*v;j--)f[j]=max(f[j],f[j-s*v]+s*w);}}}cout<<f[m]<<endl;return 0;
}

1.3.11 AcWing 8. 二维费用的背包问题

结论: f[i,j,k]=max⁡(f[i−1,j,k],f[i−1,j−v[i],k−m[i]]+w[i])f[i,j,k]=\max(f[i-1,j,k],f[i-1,j-v[i],k-m[i]]+w[i])f[i,j,k]=max(f[i−1,j,k],f[i−1,j−v[i],k−m[i]]+w[i])

思路:

  1. 状态表示
    集合:只从前 iii 个物品中选,体积不超过 jjj,重量不超过 kkk 的价值
    属性:max⁡\maxmax
  2. 状态转移
    不选第 iii 个物品:f[i−1,j,k]f[i-1,j,k]f[i−1,j,k]
    选择第 iii 个物品:f[i−1,j−v[i],k−m[i]]+w[i]f[i-1,j-v[i],k-m[i]]+w[i]f[i−1,j−v[i],k−m[i]]+w[i]
  3. 滚动数组
    由于 f[i,j,k]f[i,j,k]f[i,j,k] 只需用到当前层和上一层,则结论可转化为:f[j,k]=max⁡(f[j,k],f[j−v[i],k−m[i]]+w[i])f[j,k]=\max(f[j,k],f[j-v[i],k-m[i]]+w[i])f[j,k]=max(f[j,k],f[j−v[i],k−m[i]]+w[i])

代码:

#include<bits/stdc++.h>using namespace std;const int N = 110;int n,V,M;
int f[N][N]; //只从前 i 个物品中选,体积不超过 j,重量不超过 k 的最大价值int main()
{cin>>n>>V>>M;for(int i=0;i<n;i++){int v,m,w;cin>>v>>m>>w;for(int j=V;j>=v;j--)for(int k=M;k>=m;k--)f[j][k]=max(f[j][k],f[j-v][k-m]+w);}cout<<f[V][M]<<endl; //只从前 n 个物品中选,体积不超过 V,重量不超过 M 的最大价值return 0;
}

1.3.12 AcWing 1020. 潜水员

结论: f[i,j,k]=min⁡(f[i−1,j,k],f[i−1,j−v1,k−v2]+w)f[i,j,k]=\min(f[i-1,j,k],f[i-1,j-v1,k-v2]+w)f[i,j,k]=min(f[i−1,j,k],f[i−1,j−v1,k−v2]+w)

思路:

  1. 状态表示
    集合:只从前 iii 个物品中选,氧气至少是 jjj,氮气至少是 kkk 的重量
    属性:min⁡\minmin
  2. 状态转移
    不选第 iii 个物品:f[i−1,j,k]f[i-1,j,k]f[i−1,j,k]
    选择第 iii 个物品:f[i−1,j−v1,k−v2]+wf[i-1,j-v1,k-v2]+wf[i−1,j−v1,k−v2]+w
  3. 滚动数组
    由于 f[i,j,k]f[i,j,k]f[i,j,k] 只需用到当前层和上一层,则结论可转化为:f[j,k]=min⁡(f[j,k],f[j−v1,k−v2]+w)f[j,k]=\min(f[j,k],f[j-v1,k-v2]+w)f[j,k]=min(f[j,k],f[j−v1,k−v2]+w)

代码:

#include<bits/stdc++.h>using namespace std;const int N = 25, M = 80;int n,m,k;
int f[N][M]; //只从前 i 个物品中选,氧气至少是 j,氮气至少是 k 的最小重量int main()
{cin>>n>>m>>k;memset(f,0x3f,sizeof f);f[0][0]=0;for(int i=0;i<k;i++){int v1,v2,w;cin>>v1>>v2>>w;for(int j=n;j>=0;j--)for(int k=m;k>=0;k--)f[j][k]=min(f[j][k],f[max(0,j-v1)][max(0,k-v2)]+w);}cout<<f[n][m]<<endl; //只从前 k 个物品中选,氧气至少是 n,氮气至少是 m 的最小重量return 0;
}

1.3.13 AcWing 1013. 机器分配

思路:

  1. 状态表示
    集合:只从前 iii 组物品中选,体积不超过 jjj 的价值
    属性:max⁡\maxmax
  2. 状态转移
    选择第 iii 组物品的第 kkk 个物品:f[i,j]=max⁡{f[i−1,j−v[i][k]]+w[i][k]}(0≤k≤s[i])f[i,j]=\max\{f[i-1,j-v[i][k]]+w[i][k]\}\ (0\le k\le s[i])f[i,j]=max{f[i−1,j−v[i][k]]+w[i][k]} (0≤k≤s[i])

代码:

#include<bits/stdc++.h>using namespace std;const int N = 11, M = 16;int n,m;
int w[N][M];
int f[N][M];
int ways[N];int main()
{cin>>n>>m;for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)cin>>w[i][j];//求解最大价值      for(int i=1;i<=n;i++)for(int j=0;j<=m;j++)for(int k=0;k<=j;k++)f[i][j]=max(f[i][j],f[i-1][j-k]+w[i][k]);cout<<f[n][m]<<endl;//求解具体方案for(int i=n,j=m;i;i--)for(int k=0;k<=j;k++)if(f[i][j]==f[i-1][j-k]+w[i][k]){ways[i]=k;j-=k;break;}for(int i=1;i<=n;i++) cout<<i<<' '<<ways[i]<<endl;return 0;
}

1.3.14 AcWing 426. 开心的金明

结论: f[i,j]=max⁡(f[i−1,j],f[i−1,j−v[i]]+v[i]⋅w[i])f[i,j]=\max(f[i-1,j],f[i-1,j-v[i]]+v[i]\cdot w[i])f[i,j]=max(f[i−1,j],f[i−1,j−v[i]]+v[i]⋅w[i])

思路:

  1. 状态表示
    集合:只从前 iii 个物品中选,体积不超过 jjj 的价值
    属性:max⁡\maxmax
  2. 状态转移
    不选第 iii 个物品:f[i−1,j]f[i-1,j]f[i−1,j]
    选择第 iii 个物品:f[i−1,j−v[i]]+v[i]⋅w[i]f[i-1,j-v[i]]+v[i]\cdot w[i]f[i−1,j−v[i]]+v[i]⋅w[i]
  3. 滚动数组
    由于 f[i,j]f[i,j]f[i,j] 只需用到当前层和上一层,则结论可转化为:f[j]=max⁡(f[j],f[j−v[i]]+v[i]⋅w[i])f[j]=\max(f[j],f[j-v[i]]+v[i]\cdot w[i])f[j]=max(f[j],f[j−v[i]]+v[i]⋅w[i])

代码:

#include<bits/stdc++.h>using namespace std;const int N = 30010;int n,m;
int f[N];int main()
{cin>>m>>n;for(int i=0;i<n;i++){int v,w;cin>>v>>w;for(int j=m;j>=v;j--)f[j]=max(f[j],f[j-v]+v*w);}cout<<f[m]<<endl;return 0;
}

1.3.15 AcWing 10. 有依赖的背包问题

1.3.16 AcWing 11. 背包问题求方案数

思路:

  1. 如果 f[j]>f[j−v]+wf[j] > f[j - v] + wf[j]>f[j−v]+w,那么 f[j]=f[j],g[j]=g[j]f[j] = f[j],g[j] = g[j]f[j]=f[j],g[j]=g[j](可省略)
  2. 如果 f[j]<f[j−v]+wf[j] < f[j - v] + wf[j]<f[j−v]+w,那么 f[j]=f[j−v]+w,g[j]=g[j−v]f[j] = f[j - v]+w,g[j] = g[j - v]f[j]=f[j−v]+w,g[j]=g[j−v]
  3. 如果 f[j]=f[j−v]+wf[j] = f[j - v] + wf[j]=f[j−v]+w,那么 g[j]+=g[j−v]g[j] += g[j - v]g[j]+=g[j−v]

代码:

#include<bits/stdc++.h>using namespace std;const int N = 1010, mod = 1e9 + 7;int n,m;
int f[N]; //从前 i 个物品中选,体积不超过 j 的最大价值
int g[N]; //当 f[j] 取到最大价值时的方案数int main()
{cin>>n>>m;for(int i=0;i<=m;i++) g[i]=1;for(int i=0;i<n;i++){int v,w;cin>>v>>w;for(int j=m;j>=v;j--){if(f[j]<f[j-v]+w) //后者更大{f[j]=f[j-v]+w;g[j]=g[j-v];}else if(f[j]==f[j-v]+w) g[j]=(g[j]+g[j-v])%mod; //一样大// else //前者更大可省略// {//     f[j]=f[j];//     g[j]=g[j];// }}}cout<<g[m]<<endl;return 0;
}

1.3.17 AcWing 12. 背包问题求具体方案

思路:

  1. 状态表示
    集合:从第 i∼ni\sim ni∼n 个物品中选,体积不超过 jjj 的价值
    属性:max⁡\maxmax
  2. 状态转移
    不选第 iii 个物品:f[i+1,j]f[i+1,j]f[i+1,j]
    选择第 iii 个物品:f[i+1,j−v[i]]+w[i]f[i+1,j-v[i]]+w[i]f[i+1,j−v[i]]+w[i]

本题思路:

  1. 如果 f[i,j]=f[i+1,j],f[i,j]≠f[i+1,j−v[i]]+w[i]f[i,j] = f[i + 1,j], f[i,j] \ne f[i + 1,j - v[i]] + w[i]f[i,j]=f[i+1,j],f[i,j]=f[i+1,j−v[i]]+w[i],那么一定不选第 iii 个物品
  2. 如果 f[i,j]≠f[i+1,j],f[i,j]=f[i+1,j−v[i]]+w[i]f[i,j] \ne f[i + 1,j], f[i,j] = f[i + 1,j - v[i]] + w[i]f[i,j]=f[i+1,j],f[i,j]=f[i+1,j−v[i]]+w[i],那么一定要选第 iii 个物品
  3. 如果 f[i,j]=f[i+1,j],f[i,j]=f[i+1,j−v[i]]+w[i]f[i,j] = f[i + 1,j], f[i,j] = f[i + 1,j - v[i]] + w[i]f[i,j]=f[i+1,j],f[i,j]=f[i+1,j−v[i]]+w[i],那么一定要选第 iii 个物品
  4. 总结:只要 f[i,j]=f[i+1,j−v[i]]+w[i]f[i,j] = f[i + 1,j - v[i]] + w[i]f[i,j]=f[i+1,j−v[i]]+w[i],就一定要选第 iii 个物品

代码:

#include<bits/stdc++.h>using namespace std;const int N = 1010;int n,m;
int v[N],w[N];
int f[N][N];int main()
{cin>>n>>m;for(int i=1;i<=n;i++) cin>>v[i]>>w[i];//反着求最大价值 f[1][m]for(int i=n;i;i--)for(int j=0;j<=m;j++){f[i][j]=f[i+1][j];if(j>=v[i]) f[i][j]=max(f[i][j],f[i+1][j-v[i]]+w[i]);}//求解具体方案for(int i=1,j=m;i<=n;i++)if(j>=v[i]&&f[i][j]==f[i+1][j-v[i]]+w[i]){cout<<i<<' ';j-=v[i];}return 0;
}

1.3.18 AcWing 734. 能量石

1.3.19 AcWing 487. 金明的预算方案

思路:

  1. 将第 iii 个主件以及其携带的附件看成是第 iii 组物品
  2. 假设第 iii 个主件有 nnn 个附件,则第 iii 组物品有 2n2 ^ n2n 种决策
  3. 依次枚举每组物品,体积,决策

代码:

#include<bits/stdc++.h>#define v first
#define w secondusing namespace std;typedef pair<int,int> PII;const int N = 70, M = 32010;int n,m;
PII master[N];
vector<PII> servant[N];
int f[M];int main()
{cin>>m>>n;for(int i=1;i<=n;i++){int v,p,q;cin>>v>>p>>q;if(!q) master[i]={v,v*p};else servant[q].push_back({v,v*p});}for(int i=1;i<=n;i++) //枚举组数for(int j=m;j;j--) //枚举体积for(int k=0;k<1<<servant[i].size();k++) //枚举决策{ int v=master[i].v,w=master[i].w; //主件for(int u=0;u<servant[i].size();u++) //附件if(k>>u&1){v+=servant[i][u].v;w+=servant[i][u].w;}if(j>=v) f[j]=max(f[j],f[j-v]+w);}cout<<f[m]<<endl;return 0;
}

1.4 状态机模型(3/5)

1.4.1 AcWing 1049. 大盗阿福

思路:

  1. 状态表示
    集合:只从前 iii 个物品中选,f[i][0]f[i][0]f[i][0] 表示不选第 iii 个物品,f[i][1]f[i][1]f[i][1] 表示选择第 iii 个物品
    属性:max⁡\maxmax
  2. 状态转移
    不选第 iii 个物品:f[i][0]=max⁡(f[i−1][0],f[i−1][1])f[i][0]=\max(f[i-1][0],f[i-1][1])f[i][0]=max(f[i−1][0],f[i−1][1])
    选择第 iii 个物品:f[i][1]=f[i−1][0]+w[i]f[i][1]=f[i-1][0]+w[i]f[i][1]=f[i−1][0]+w[i]

代码:

#include<bits/stdc++.h>using namespace std;const int N = 100010;int n;
int w[N];
int f[N][2];int main()
{int T;cin>>T;while(T--){cin>>n;for(int i=1;i<=n;i++) cin>>w[i];f[0][0]=f[0][1]=0;for(int i=1;i<=n;i++){f[i][0]=max(f[i-1][0],f[i-1][1]); //不选第 i 个物品f[i][1]=f[i-1][0]+w[i]; //选择第 i 个物品}cout<<max(f[n][0],f[n][1])<<endl;}return 0;
}

1.4.2 AcWing 1057. 股票买卖 IV

思路:

  1. 状态表示
    集合:只从前 iii 只股票中选,正在进行第 jjj 次交易,f[i,j,0]f[i,j,0]f[i,j,0] 表示手中无货,f[i,j,1]f[i,j,1]f[i,j,1] 表示手中有货
  2. 状态转移
    手中无货:f[i,j,0]=max⁡(f[i−1,j,0],f[i−1,j,1]+w[i])f[i,j,0]=\max(f[i-1,j,0],f[i-1,j,1]+w[i])f[i,j,0]=max(f[i−1,j,0],f[i−1,j,1]+w[i])
    手中有货:f[i,j,1]=max⁡(f[i−1,j,1],f[i−1,j−1,0]−w[i])f[i,j,1]=\max(f[i-1,j,1],f[i-1,j-1,0]-w[i])f[i,j,1]=max(f[i−1,j,1],f[i−1,j−1,0]−w[i])
  3. 优化
    手中无货:f[j,0]=max⁡(f[j,0],f[j,1]+w[i])f[j,0]=\max(f[j,0],f[j,1]+w[i])f[j,0]=max(f[j,0],f[j,1]+w[i])
    手中有货:f[j,1]=max⁡(f[j,1],f[j−1,0]−w[i])f[j,1]=\max(f[j,1],f[j-1,0]-w[i])f[j,1]=max(f[j,1],f[j−1,0]−w[i])
  4. 提醒
    一次完整的买入卖出才算一次交易的真正结束

代码:

#include<bits/stdc++.h>using namespace std;const int N = 100010, M = 110;int n,m;
int w[N];
int f[M][2];int main()
{cin>>n>>m;for(int i=1;i<=n;i++) cin>>w[i];memset(f,-0x3f,sizeof f);f[0][0]=0;for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){f[j][0]=max(f[j][0],f[j][1]+w[i]); //手中无货f[j][1]=max(f[j][1],f[j-1][0]-w[i]); //手中有货}int res=0;for(int i=1;i<=m;i++) res=max(res,f[i][0]);cout<<res<<endl;return 0;
}

1.4.3 AcWing 1058. 股票买卖 V

思路:

  1. 状态表示
    集合:从前 iii 只股票中选,目前是 jjj 状态,f[i,0]f[i,0]f[i,0] 表示手中有货,f[i,1]f[i,1]f[i,1] 表示手中无货的第一天,f[i,2]f[i,2]f[i,2] 表示手中无货的大于等于第二天
    属性:max⁡\maxmax
  2. 状态转移
    手中有货:f[i,0]=max⁡(f[i−1,0],f[i−1,2]−w[i])f[i,0]=\max(f[i-1,0],f[i-1,2]-w[i])f[i,0]=max(f[i−1,0],f[i−1,2]−w[i])
    手中无货的第一天:f[i,1]=f[i−1,0]+w[i]f[i,1]=f[i-1,0]+w[i]f[i,1]=f[i−1,0]+w[i]
    手中无货的大于等于第二天:f[i,2]=max⁡(f[i−1,1],f[i−1,2])f[i,2]=\max(f[i-1,1],f[i-1,2])f[i,2]=max(f[i−1,1],f[i−1,2])

代码:

#include<bits/stdc++.h>using namespace std;const int N = 100010;int n;
int w[N];
int f[N][3];int main()
{cin>>n;for(int i=1;i<=n;i++) cin>>w[i];memset(f,-0x3f,sizeof f);f[0][2]=0;for(int i=1;i<=n;i++){//手中无货f[i][0]=max(f[i-1][0],f[i-1][2]-w[i]);//手中无货的第一天f[i][1]=f[i-1][0]+w[i];//手中无货的大于等于第二天f[i][2]=max(f[i-1][1],f[i-1][2]);}cout<<max(f[n][1],f[n][2])<<endl;return 0;
}

1.4.4 AcWing 1052. 设计密码

1.4.5 AcWing 1053. 修复DNA

1.5 状态压缩DP(3/5)

1.5.1 AcWing 1064. 小国王

思路:

  1. 状态表示
    集合:前 iii 行已摆好,且已摆放好 jjj 个国王,第 iii 行的状态是 aaa 的方案
    属性:CountCountCount
  2. 状态转移
    aaa 是第 iii 行的状态,bbb 是第 i−1i-1i−1 行的状态,bbb 是 aaa 所能够转移的状态
    f[i][j][a]+=f[i−1][j−cnt[a]][b]f[i][j][a]+=f[i-1][j-cnt[a]][b]f[i][j][a]+=f[i−1][j−cnt[a]][b]

代码:

#include<bits/stdc++.h>using namespace std;typedef long long LL;const int N = 12, M = 1 << N, K = 110;int n,m;
int cnt[M]; //该状态中 1 的个数
LL f[N][K][M]; //前 i 行已摆好,且已摆放好 j 个国王,第 i 行的状态是 a 的方案
vector<int> state; //合法的状态
vector<int> head[M]; //该状态能够转移的状态//判断该状态是否存在相邻的 1
bool check(int state)
{return !(state&state>>1);
}//计算出该状态中 1 的数量
int count(int state)
{int res=0;for(int i=0;i<n;i++) res+=state>>i&1;return res;
}int main()
{cin>>n>>m;//筛选出合法状态for(int i=0;i<1<<n;i++)if(check(i)){state.push_back(i);cnt[i]=count(i);}//找出该合法状态可以转移到的合法状态for(auto a : state)for(auto b : state)if(!(a&b)&&check(a|b))head[a].push_back(b);f[0][0][0]=1;    for(int i=1;i<=n+1;i++)for(int j=0;j<=m;j++)for(auto a : state) //枚举第i行的状态for(auto b : head[a]) //枚举第i-1行的状态if(j>=cnt[a])f[i][j][a]+=f[i-1][j-cnt[a]][b];cout<<f[n+1][m][0]<<endl;return 0;
}

1.5.2 AcWing 327. 玉米田

思路:

  1. 状态表示
    集合:前i行已摆好,且第i行的状态是a的方案
    属性:CountCountCount
  2. 状态转移
    aaa 是第 iii 行的状态,bbb 是第 i−1i-1i−1 行的状态,bbb 是 aaa 所能够转移的状态
    f[i][a]+=f[i−1[b]f[i][a]+=f[i-1[b]f[i][a]+=f[i−1[b]

代码:

#include<bits/stdc++.h>using namespace std;const int N = 14, M = 1 << N, mod = 1e8;int n,m;
int g[N];
int f[N][M];
vector<int> state;
vector<int> head[M];//判断该状态是否存在相邻的 1
bool check(int state)
{return !(state&state>>1);
}int main()
{cin>>n>>m;for(int i=1;i<=n;i++)for(int j=0;j<m;j++){int t;cin>>t;g[i]+=!t<<j;}//筛选出合法的状态    for(int i=0;i<1<<m;i++)if(check(i))state.push_back(i);//计算出该状态能够转移的状态for(auto a : state)for(auto b : state)if(!(a&b))head[a].push_back(b);f[0][0]=1;for(int i=1;i<=n+1;i++) //枚举每一行for(auto a : state) //枚举所有状态if(!(g[i]&a)) //该行状态是否与枚举的状态冲突for(auto b : head[a]) //枚举前一行状态,且是该行状态能够转移到的状态f[i][a]=(f[i][a]+f[i-1][b])%mod; //状态 b 转移到状态 acout<<f[n+1][0]<<endl;return 0;
}

1.5.3 AcWing 292. 炮兵阵地

代码:

#include<bits/stdc++.h>using namespace std;const int N = 110, M = 1 << 10;int n,m;
int g[N];
int cnt[M];
int f[2][M][M];
vector<int> state;
vector<int> head[M];//三个连续的数中不能存在两个1
bool check(int state)
{return !(state&state>>1||state&state>>2);
}//统计该状态中1的个数
int count(int state)
{int res=0;for(int i=0;i<m;i++) res+=state>>i&1;return res;
}int main()
{cin>>n>>m;for(int i=1;i<=n;i++)for(int j=0;j<m;j++){char c;cin>>c;g[i]+=(c=='H')<<j;}//筛选出合法状态for(int i=0;i<1<<m;i++)if(check(i)){state.push_back(i);cnt[i]=count(i);}//找出该状态可以转移的状态for(auto a : state)for(auto b : state)if(!(a&b))head[a].push_back(b);for(int i=1;i<=n+2;i++)for(auto a : state)for(auto b : head[a])for(auto c : head[b]){if(g[i]&a||g[i-1]&b) continue; //状态与山地之间不能有交集if(a&b||a&c||b&c) continue; //状态之间不能有交集f[i&1][a][b]=max(f[i&1][a][b],f[i-1&1][b][c]+cnt[a]); //滚动数组}cout<<f[n+2&1][0][0]<<endl;return 0;
}

1.5.4 AcWing 524. 愤怒的小鸟

1.5.5 AcWing 529. 宝藏

1.6 区间DP(4/5)

1.6.1 AcWing 1068. 环形石子合并

思路:

  1. 状态表示
    集合:区间 [l,r][l,r][l,r] 内的石子合并后的得分
    属性:min⁡\minmin
  2. 状态转移
    f[l][r]=min⁡{f[l][k]+f[k+1][r]+s[r]−s[l−1]}(l≤k<r)f[l][r]=\min\{f[l][k]+f[k+1][r]+s[r]-s[l-1]\}(l\le k < r)f[l][r]=min{f[l][k]+f[k+1][r]+s[r]−s[l−1]}(l≤k<r)
  3. Finally
    环形石子合并的最小值 =min⁡{f[i][i+n−1]}(1≤i≤n)=\min\{f[i][i+n-1]\}(1\le i\le n)=min{f[i][i+n−1]}(1≤i≤n)
    环形石子合并的最大值 =max⁡{g[i][i+n−1]}(1≤i≤n)=\max\{g[i][i+n-1]\}(1\le i\le n)=max{g[i][i+n−1]}(1≤i≤n)

代码:

#include<bits/stdc++.h>using namespace std;const int N = 410, INF = 0x3f3f3f3f;int n;
int a[N],s[N];
int f[N][N],g[N][N];int main()
{cin>>n;for(int i=1;i<=n;i++){cin>>a[i];a[n+i]=a[i]; //再加上一条链}for(int i=1;i<=n*2;i++) s[i]=s[i-1]+a[i]; //前缀和for(int len=2;len<=n;len++) //枚举区间长度,区间长度为 1 的时候不需要合并for(int l=1;l+len-1<=n*2;l++) //枚举左端点{int r=l+len-1; //右端点f[l][r]=INF;for(int k=l;k<r;k++) //枚举该区间的分界点{f[l][r]=min(f[l][r],f[l][k]+f[k+1][r]+s[r]-s[l-1]);g[l][r]=max(g[l][r],g[l][k]+g[k+1][r]+s[r]-s[l-1]);}}int minv=INF,maxv=-INF;for(int i=1;i<=n;i++){minv=min(minv,f[i][i+n-1]);maxv=max(maxv,g[i][i+n-1]);}cout<<minv<<'\n'<<maxv<<endl;return 0;
}

1.6.2 AcWing 320. 能量项链

思路:

  1. 状态表示
    集合:区间[l,r][l,r][l,r]内的珠子合并后的得分
    属性:max⁡\maxmax
  2. 状态转移
    f[l][r]=max⁡{f[l][k]+f[k][r]+w[l]⋅w[k]⋅w[r]}(l<k<r)f[l][r]=\max\{f[l][k]+f[k][r]+w[l]\cdot w[k]\cdot w[r]\}(l < k < r)f[l][r]=max{f[l][k]+f[k][r]+w[l]⋅w[k]⋅w[r]}(l<k<r)
  3. Finally
    最大值 =max⁡{f[i][i+n]}(1≤i≤n)=\max\{f[i][i+n]\}(1\le i\le n)=max{f[i][i+n]}(1≤i≤n)

代码:

#include<bits/stdc++.h>using namespace std;const int N = 210;int n;
int w[N];
int f[N][N];int main()
{cin>>n;for(int i=1;i<=n;i++){cin>>w[i];w[i+n]=w[i];}for(int len=3;len<=n+1;len++) //枚举区间长度,当区间长度<=2时不需要合并for(int l=1;l+len-1<=n*2;l++) //枚举左端点{int r=l+len-1; //右端点for(int k=l+1;k<r;k++) //枚举该区间的分界点f[l][r]=max(f[l][r],f[l][k]+f[k][r]+w[l]*w[k]*w[r]);}int res=0;for(int i=1;i<=n;i++) res=max(res,f[i][i+n]);cout<<res<<endl;return 0;
}

1.6.3 AcWing 479. 加分二叉树

思路:

  1. 状态表示
    集合:区间 [l,r][l,r][l,r] 内的节点合并后可以得到的分数
    属性:max⁡\maxmax
  2. 状态转移
    f[l][r]=max⁡{f[l][k−1]⋅f[k+1][r]+w[k]}(l<k<r)f[l][r]=\max\{f[l][k-1]\cdot f[k+1][r]+w[k]\}(l<k<r)f[l][r]=max{f[l][k−1]⋅f[k+1][r]+w[k]}(l<k<r)

代码:

#include<bits/stdc++.h>using namespace std;const int N = 30;int n;
int w[N];
int f[N][N],g[N][N];void dfs(int l,int r)
{if(l>r) return;cout<<g[l][r]<<' '; //输出根节点dfs(l,g[l][r]-1); //递归左边dfs(g[l][r]+1,r); //递归右边
}int main()
{cin>>n;for(int i=1;i<=n;i++) cin>>w[i];for(int len=1;len<=n;len++) //枚举区间长度for(int l=1;l+len-1<=n;l++) //枚举左端点{int r=l+len-1; //右端点if(len==1){f[l][r]=w[l];g[l][r]=l;}else{for(int k=l;k<=r;k++) //枚举分界点{int left=k==l?1:f[l][k-1];int right=k==r?1:f[k+1][r];int score=left*right+w[k];if(score>f[l][r]){f[l][r]=score;g[l][r]=k;}}}}cout<<f[1][n]<<endl;dfs(1,n);return 0;
}

1.6.4 AcWing 1069. 凸多边形的划分

思路:

  1. 状态表示
    集合:区间 [l,r][l,r][l,r] 内的顶点划分三角形后各权值乘积之和
    属性:min⁡\minmin
  2. 状态转移
    f[l][r]=min⁡{f[l][k]+f[k][r]+w[l]⋅w[k]⋅w[r]}(l<k<r)f[l][r]=\min\{f[l][k]+f[k][r]+w[l]\cdot w[k]\cdot w[r]\}(l < k < r)f[l][r]=min{f[l][k]+f[k][r]+w[l]⋅w[k]⋅w[r]}(l<k<r)
  3. 为什么不用断环成链?
    因为划分三角形后,凸多边形的所有边都一定是某个三角形的一边,即每条边都一定存在

代码:

#include<bits/stdc++.h>using namespace std;typedef long long LL;const int N = 55, M = 30;int n;
int w[N];
LL f[N][N][M];
LL temp[M];//高精度乘法
void mul(LL a[],LL b)
{LL c[M]={0},t=0;for(int i=0;i<M;i++){t+=a[i]*b;c[i]=t%10;t/=10;}memcpy(a,c,sizeof c);
}//高精度加法
void add(LL a[],LL b[])
{LL c[M]={0};for(int i=0,t=0;i<M;i++){t+=a[i]+b[i];c[i]=t%10;t/=10;}memcpy(a,c,sizeof c);
}//高精度比较
int cmp(LL a[],LL b[])
{for(int i=M-1;i>=0;i--){if(a[i]>b[i]) return 1;if(a[i]<b[i]) return -1;}return 0;
}//高精度打印
void print(LL a[])
{int k=M-1;while(k&&!a[k]) k--;while(k>=0) cout<<a[k--];
}int main()
{cin>>n;for(int i=1;i<=n;i++) cin>>w[i];for(int len=3;len<=n;len++) //枚举区间长度,至少要三个顶点才能合并for(int l=1;l+len-1<=n;l++) //枚举左端点{int r=l+len-1; //右端点f[l][r][M-1]=1; //正无穷for(int k=l+1;k<r;k++) //枚举分界点{memset(temp,0,sizeof temp);temp[0]=w[l];mul(temp,w[k]);mul(temp,w[r]);add(temp,f[l][k]);add(temp,f[k][r]);if(cmp(temp,f[l][r])<0) memcpy(f[l][r],temp,sizeof temp); //上述代码等价于f[l][r]=min(f[l][r],f[l][k]+f[k][r]+w[l]*w[k]*w[r]);}}print(f[1][n]);return 0;
}

1.6.5 AcWing 321. 棋盘分割

1.7 树形DP(3/6)

1.7.1 AcWing 1072. 树的最长路径

思路:

  1. 随便找一个点 aaa,从点 aaa 出发找到能到达的最远的点 bbb,再从点 bbb 出发找到能到达的最远的点 ccc,则 bcbcbc 为直径
  2. 枚举所有的根结点,找出以根结点为顶点的最大长度的最大值 ansansans
  3. 对于每个根结点,往下找最大距离 d1d1d1 和次大距离 d2d2d2,则以这个根结点为顶点的最大长度 =d1+d2= d1 + d2=d1+d2

代码:

#include<bits/stdc++.h>using namespace std;const int N = 10010, M = N * 2;int n;
int h[N],e[M],w[M],ne[M],idx;
int ans;void add(int a,int b,int c)
{e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}int dfs(int u,int fa)
{int d1=0,d2=0;for(int i=h[u];~i;i=ne[i]){int j=e[i];if(j==fa) continue; //不能往上回溯int d=dfs(j,u)+w[i]; //子结点往下能到达的最大距离 + 根结点到该子结点的距离if(d>d1) d2=d1,d1=d; //更新最大距离和次大距离else if(d>d2) d2=d; //更新次大距离}ans=max(ans,d1+d2); //直径 = 最大距离 + 次大距离return d1; //返回该根结点能往下到达的最大距离
}int main()
{cin>>n;memset(h,-1,sizeof h);for(int i=0;i<n-1;i++){int a,b,c;cin>>a>>b>>c;add(a,b,c);add(b,a,c);}dfs(1,-1);cout<<ans<<endl;return 0;
}

1.7.2 AcWing 1073. 树的中心

思路:

  1. d1[u]d1[u]d1[u] 表示从 uuu 往下走能到达的最大路径,d2[u]d2[u]d2[u] 表示从 uuu 往下走能到达的次大路径
  2. p1[u]p1[u]p1[u] 表示从 uuu 出发的最大路径需要经过 uuu 的儿子节点 jjj,up[u]up[u]up[u] 表示从 uuu 往上走能到达的最大路径
  3. jjj 往上走到父节点 uuu,从 uuu 可以往上走,也可以往下走不经过 jjj 的路径
  4. 枚举所有点,找出该点能到达的最远距离 max⁡(d1[u],up[u])\max(d1[u], up[u])max(d1[u],up[u]) 的最小值

代码:

#include<bits/stdc++.h>using namespace std;const int N = 10010, M = N * 2, INF = 0x3f3f3f3f;int n;
int h[N],e[M],w[M],ne[M],idx;
int d1[N],d2[N],p1[N],up[N];void add(int a,int b,int c)
{e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}int dfs_down(int u,int fa)
{d1[u]=d2[u]=0;for(int i=h[u];~i;i=ne[i]){int j=e[i];if(j==fa) continue;int d=dfs_down(j,u)+w[i];if(d>d1[u]){d2[u]=d1[u],d1[u]=d;p1[u]=j;}else if(d>d2[u]) d2[u]=d;}return d1[u];
}void dfs_up(int u,int fa)
{for(int i=h[u];~i;i=ne[i]){int j=e[i];if(j==fa) continue;if(p1[u]==j) up[j]=max(up[u],d2[u])+w[i]; //父节点的最大路径经过当前点,则用次大路径比较else up[j]=max(up[u],d1[u])+w[i]; //否则用最大路径比较dfs_up(j,u);}
}int main()
{cin>>n;memset(h,-1,sizeof h);for(int i=0;i<n-1;i++){int a,b,c;cin>>a>>b>>c;add(a,b,c);add(b,a,c);}dfs_down(1,-1);dfs_up(1,-1);int res=INF;for(int i=1;i<=n;i++) res=min(res,max(d1[i],up[i]));cout<<res<<endl;return 0;
}

1.7.3 AcWing 1075. 数字转换

思路:

  1. 从倍数角度出发,让 iii 的倍数的 sum[i⋅j]sum[i \cdot j]sum[i⋅j] 加上 iii
  2. 每个 iii 只会有一个 sum[i]sum[i]sum[i],即 sum[i]sum[i]sum[i] 是 iii 的父节点
  3. 最后就是求一个树的直径

代码:

#include<bits/stdc++.h>using namespace std;const int N = 50010;int n;
int sum[N];
int h[N],e[N],ne[N],idx;
int ans;void add(int a,int b)
{e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}int dfs(int u)
{int d1=0,d2=0;for(int i=h[u];~i;i=ne[i]){int j=e[i];int d=dfs(j)+1;if(d>d1) d2=d1,d1=d;else if(d>d2) d2=d;}ans=max(ans,d1+d2); //更新最大值return d1;
}int main()
{cin>>n;for(int i=1;i<=n;i++)for(int j=2;j<=n/i;j++)sum[i*j]+=i;memset(h,-1,sizeof h);for(int i=2;i<=n;i++)if(sum[i]<i)add(sum[i],i); // i 的约数之和向 i 连一条边dfs(1);cout<<ans<<endl;return 0;
}

1.7.4 AcWing 1074. 二叉苹果树

1.7.5 AcWing 323. 战略游戏

1.7.6 AcWing 1077. 皇宫看守

1.8 数位DP(0/6)

1.9 单调队列优化DP(0/6)

1.10 斜率优化DP(0/4)

2. 搜索(9/25)

2.1 Flood Fill(3/3)

2.1.1 AcWing 1097. 池塘计数

思路:

  1. 枚举所有点,符合条件就 dfsdfsdfs 该点
  2. 将该点变成其他不符合条件的符号,这样就不用 ststst 数组来记录了
  3. 枚举八个方向,符合条件的点就直接 dfsdfsdfs
  4. cntcntcnt 表示连通块个数,dfs()dfs()dfs() 调用几次就说明有多少个连通块

代码:

#include<bits/stdc++.h>using namespace std;const int N = 1010;int n,m;
char g[N][N];
int dx[8]={-1,-1,-1,0,0,1,1,1};
int dy[8]={-1,0,1,-1,1,-1,0,1};void dfs(int x,int y)
{//一种巧妙的方法,这样就不同st数组来记录了g[x][y]='.';//枚举八个方向for(int i=0;i<8;i++)if(g[x+dx[i]][y+dy[i]]=='W')dfs(x+dx[i],y+dy[i]);
}int main()
{cin>>n>>m;for(int i=0;i<n;i++) cin>>g[i];int cnt=0;for(int i=0;i<n;i++)for(int j=0;j<m;j++)if(g[i][j]=='W'){dfs(i,j);cnt++;}cout<<cnt<<endl;return 0;
}

2.1.2 AcWing 1098. 城堡问题

思路:

  1. cntcntcnt 表示连通块个数,areaareaarea 表示连通块最大面积
  2. 依次枚举每个点,如果该点没被记录过,就将其入队并记录该点
  3. 取出队头,area++area++area++
  4. 枚举四个方向,左西,上北,右东,下南
  5. 将符合的点入队,并记录该点
  6. cntcntcnt 就是 bfs()bfs()bfs() 的次数,areaareaarea 就是取各个连通块面积的最大值

代码:

#include<bits/stdc++.h>using namespace std;typedef pair<int,int> PII;const int N = 55;int n,m;
int g[N][N];
bool st[N][N];
int dx[4]={0,-1,0,1};
int dy[4]={-1,0,1,0};int bfs(int x,int y)
{queue<PII> q;q.push({x,y});st[x][y]=true;int area=0;while(q.size()){auto t=q.front();q.pop();area++;for(int i=0;i<4;i++){int a=t.first+dx[i],b=t.second+dy[i];//超出边界if(a<0||a>=n||b<0||b>=m) continue;//撞墙了if(g[t.first][t.second]>>i&1) continue;//该点已走过if(st[a][b]) continue;//扔入队列并记录q.push({a,b});st[a][b]=true;}}return area;
}int main()
{cin>>n>>m;for(int i=0;i<n;i++)for(int j=0;j<m;j++)cin>>g[i][j];int cnt=0,area=0;for(int i=0;i<n;i++)for(int j=0;j<m;j++)if(!st[i][j]){cnt++; //房间总数area=max(area,bfs(i,j)); //最大房间的面积}cout<<cnt<<'\n'<<area<<endl;return 0;
}

2.1.3 AcWing 1106. 山峰和山谷

思路:

  1. 山峰:没有任何山比他高;山谷:没有任何山比他低;故找是否有不符合其条件的点即可
  2. 枚举所有点,如果该点未被记录,就将其加入队列并记录该点
  3. 取出队头,枚举该点的八个方向,观察是否有点比他高或比他低
  4. 如果高度相等并且该点未被记录过,就加入队列并记录该点
  5. 等所有点枚举完后,山峰和山谷的数量就出来了

代码:

#include<bits/stdc++.h>#define x first
#define y secondusing namespace std;typedef pair<int,int> PII;const int N = 1010;int n;
int h[N][N];
bool st[N][N];
int dx[8]={-1,-1,-1,0,0,1,1,1};
int dy[8]={-1,0,1,-1,1,-1,0,1};void bfs(int x,int y,bool &has_higher,bool &has_lower)
{queue<PII> q;q.push({x,y});st[x][y]=true;while(q.size()){//取出队头auto t=q.front();q.pop();//枚举八个方向for(int i=0;i<8;i++){int a=t.x+dx[i],b=t.y+dy[i];//超出边界if(a<0||a>=n||b<0||b>=n) continue;//高度不等if(h[a][b]!=h[t.x][t.y]) {if(h[a][b]>h[t.x][t.y]) has_higher=true;else has_lower=true;}//高度相等且未入队else if(!st[a][b]){q.push({a,b});st[a][b]=true;}}}
}int main()
{cin>>n;for(int i=0;i<n;i++)for(int j=0;j<n;j++)cin>>h[i][j];int peak=0,valley=0; for(int i=0;i<n;i++)for(int j=0;j<n;j++)if(!st[i][j]){bool has_higher=false,has_lower=false;bfs(i,j,has_higher,has_lower);if(!has_higher) peak++; //山峰if(!has_lower) valley++; //山谷}cout<<peak<<' '<<valley<<endl;return 0;
}

2.2 最短路模型(3/3)

2.2.1 AcWing 1076. 迷宫问题

思路:

  1. 因为要正向记录路径,所以 bfsbfsbfs 可以反向搜
  2. preprepre 数组不仅可以记录该点上一步状态,还能判断该点是否走过
  3. 取出队头,枚举四个方向,符合条件的加入队列,并记录上一步状态
  4. 最后从 (0,0)(0,0)(0,0) 开始还原路径

代码:

#include<bits/stdc++.h>#define x first
#define y secondusing namespace std;typedef pair<int,int> PII;const int N = 1010;int n;
int g[N][N];
PII pre[N][N];
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};void bfs(int sx,int sy)
{queue<PII> q;q.push({sx,sy});//数据初始化memset(pre,-1,sizeof pre);pre[sx][sy]={0,0};while(q.size()){//取出队头auto t=q.front();q.pop();//枚举四个方向for(int i=0;i<4;i++){int a=t.x+dx[i],b=t.y+dy[i];//超出边界if(a<0||a>=n||b<0||b>=n) continue;//该点不可走或该点已走过if(g[a][b]||pre[a][b].x!=-1) continue;//符合条件,将该点放入队列,并记录上一步状态q.push({a,b});pre[a][b]=t;}}
}int main()
{cin>>n;for(int i=0;i<n;i++)for(int j=0;j<n;j++) cin>>g[i][j];bfs(n-1,n-1); //从后往前bfs,因为要正向记录路径 PII end={0,0}; //从(0,0)点开始还原路径while(true){cout<<end.x<<' '<<end.y<<endl;if(end.x==n-1&&end.y==n-1) break;end=pre[end.x][end.y];}return 0;
}

2.2.2 AcWing 188. 武士风度的牛

思路:

  1. 找到 KKK 的坐标,对其做 bfsbfsbfs
  2. 初始化队列 qqq 和 distdistdist 数组,并把该点放入队列和更新距离
  3. 枚举日子格八个方向,将符合条件的点放入队列并更新距离
  4. 如果找到终点 HHH,就返回距离

代码:

#include<bits/stdc++.h>#define x first
#define y secondusing namespace std;typedef pair<int,int> PII;const int N = 160;int n,m;
char g[N][N];
int dist[N][N];
int dx[8]={-2,-1,1,2,2,1,-1,-2};
int dy[8]={1,2,2,1,-1,-2,-2,-1};int bfs(int sx,int sy)
{//初始化队列queue<PII> q;q.push({sx,sy});//初始化dist数组,记录距离并判断该点是否走过memset(dist,-1,sizeof dist);dist[sx][sy]=0;while(q.size()){//取出队头auto t=q.front();q.pop();//枚举八个方向for(int i=0;i<8;i++){int a=t.x+dx[i],b=t.y+dy[i];//超出边界if(a<0||a>=n||b<0||b>=m) continue;//有障碍或该点已走过if(g[a][b]=='*'||dist[a][b]!=-1) continue; //到达终点,返回距离if(g[a][b]=='H') return dist[t.x][t.y]+1;//符合条件,放入队列并更新距离q.push({a,b});dist[a][b]=dist[t.x][t.y]+1;}}return -1;
}int main()
{cin>>m>>n;for(int i=0;i<n;i++) cin>>g[i];//找到K的坐标,然后对该点做bfsfor(int i=0;i<n;i++)for(int j=0;j<m;j++)if(g[i][j]=='K')cout<<bfs(i,j)<<endl;return 0;
}

2.2.3 AcWing 1100. 抓住那头牛

思路:

  1. 农民所走的点的范围:[0,N)[0, N)[0,N)
  2. 找到起点放入队列,并更新起点步数
  3. 三种操作:t+1,t−1,t×2t + 1,t - 1,t \times 2t+1,t−1,t×2
  4. 符合范围且该点未走过,则放入队列并更新步数
  5. 到达终点 kkk,则返回步数

代码:

#include<bits/stdc++.h>using namespace std;const int N = 100010;int n,k;
int dist[N];int bfs()
{//把起点放入队列,然后bfsqueue<int> q;q.push(n);//初始化dist数组,并更新起点步数memset(dist,-1,sizeof dist);dist[n]=0;while(q.size()){//取出队头auto t=q.front();q.pop();//到达终点,返回步数if(t==k) return dist[t];//执行 t + 1,符合条件且该点未走过,则放入队列并更新步数if(t+1<N&&dist[t+1]==-1){q.push(t+1);dist[t+1]=dist[t]+1;}//执行 t - 1,符合条件且该点未走过,则放入队列并更新步数if(t-1>=0&&dist[t-1]==-1) {q.push(t-1);dist[t-1]=dist[t]+1;}//执行 t * 2,符合条件且该点未走过,则放入队列并更新步数if(t*2<N&&dist[t*2]==-1){q.push(t*2);dist[t*2]=dist[t]+1;}}return -1;
}int main()
{cin>>n>>k;cout<<bfs()<<endl;return 0;
}

2.3 多源BFS(1/1)

2.3.1 AcWing 173. 矩阵距离

思路:

  1. 将所有起点放入队列,并将他们的距离置为 000
  2. 取出队头,枚举四个方向,将符合条件的点放入队列,并更新距离

代码:

#include<bits/stdc++.h>#define x first
#define y secondusing namespace std;typedef pair<int,int> PII;const int N = 1010;int n,m;
char g[N][N];
int dist[N][N];
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};void bfs()
{queue<PII> q;memset(dist,-1,sizeof dist);//将所有'1'放入队列,并更新距离for(int i=0;i<n;i++)for(int j=0;j<m;j++)if(g[i][j]=='1'){q.push({i,j});dist[i][j]=0;}while(q.size()){//取出队头auto t=q.front();q.pop();//枚举四个方向for(int i=0;i<4;i++){int a=t.x+dx[i],b=t.y+dy[i];//超出边界   if(a<0||a>=n||b<0||b>=m) continue;//该点未被走过if(dist[a][b]!=-1) continue;//将符合条件的点放入队列,并更新距离q.push({a,b});dist[a][b]=dist[t.x][t.y]+1;}}
}int main()
{cin>>n>>m;for(int i=0;i<n;i++) cin>>g[i];bfs();for(int i=0;i<n;i++){for(int j=0;j<m;j++) cout<<dist[i][j]<<' ';cout<<endl;}return 0;
}

2.4 最小步数模型(1/1)

2.4.1 AcWing 1107. 魔板

思路:

  1. 跟八数码一样都是求最小步数,需要状态转换
  2. distdistdist 需要记录到达该状态需要的步数,preprepre 需要记录到达该状态的操作和上一步状态
  3. 枚举三种状态,如果之前没有出现过,就更新 distdistdist 和 preprepre
  4. 如果到达目标状态,就直接返回,否则,放入队列

代码:

#include<bits/stdc++.h>using namespace std;char g[2][4];
unordered_map<string,int> dist;
unordered_map<string,pair<char,string>> pre;//将一行变成两行
void set1(string state)
{for(int i=0;i<4;i++) g[0][i]=state[i];for(int i=3,j=4;i>=0;i--,j++) g[1][i]=state[j];
}//将两行变成一行
string get()
{string res;for(int i=0;i<4;i++) res+=g[0][i];for(int i=3;i>=0;i--) res+=g[1][i];return res;
}//交换上下两行
string moveA(string state)
{set1(state);for(int i=0;i<4;i++) swap(g[0][i],g[1][i]);return get();
}//将最右边的一列插入到最左边
string moveB(string state)
{set1(state);char v0=g[0][3],v1=g[1][3];for(int i=3;i>0;i--)for(int j=0;j<2;j++) g[j][i]=g[j][i-1];g[0][0]=v0,g[1][0]=v1;return get();
}//魔板中央对的4个数作顺时针旋转
string moveC(string state)
{set1(state);char v=g[0][1];g[0][1]=g[1][1];g[1][1]=g[1][2];g[1][2]=g[0][2];g[0][2]=v;return get();
}void bfs(string start,string end)
{if(start==end) return;queue<string> q;q.push(start);dist[start]=0;while(q.size()){auto t=q.front();q.pop();//由队头转换而来的三种状态string str[3];str[0]=moveA(t);str[1]=moveB(t);str[2]=moveC(t);for(int i=0;i<3;i++)if(!dist.count(str[i])){//更新步数dist[str[i]]=dist[t]+1;//记录操作和上一步状态pre[str[i]]={char('A'+i),t};//到达目标状态if(str[i]==end) return;//符合条件,放入队列q.push(str[i]);}}
}int main()
{string start="12345678",end;for(int i=0;i<8;i++){char c;cin>>c;end+=c;}bfs(start,end);cout<<dist[end]<<endl;//推路径string res;while(start!=end){res+=pre[end].first;end=pre[end].second;}//因为是从后往前推,所以要反转一下reverse(res.begin(),res.end());if(res.size()) cout<<res<<endl;return 0;
}

2.5 双端队列广搜(0/1)

2.5.1 AcWing 175. 电路维修

2.6 双向广搜(0/1)

2.6.1 AcWing 190. 字串变换

2.7 A*(1/2)

2.7.1 AcWing 178. 第K短路

2.7.2 AcWing 179. 八数码

代码:

#include<bits/stdc++.h>#define x first
#define y secondusing namespace std;typedef pair<int,string> PIS;
typedef pair<char,string> PCS;//从该状态到达目标状态的估价函数:各个点与目标状态的曼哈顿距离之和
int f(string state)
{int res=0;for(int i=0;i<9;i++)if(state[i]!='x'){int t=state[i]-'1';res+=abs(i/3-t/3)+abs(i%3-t%3);}return res;
}string bfs(string start)
{//枚举四个方向的操作char op[5]="urdl";//目标字符串string end="12345678x";//枚举四个方向的方位int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};//初始化dist数组,更新起点的距离unordered_map<string,int> dist;dist[start]=0;//记录到达此状态的操作和上一步状态unordered_map<string,PCS> pre;//初始化小根堆,第一元素:从起点到该状态的真实距离+该状态到达目标状态的估价距离;第二元素:该状态priority_queue<PIS,vector<PIS>,greater<PIS>> heap;heap.push({f(start),start});while(heap.size()){//取出堆顶:从起点到达终点的估价距离最小auto t=heap.top();heap.pop();//取出该状态string state=t.y;//到达目标字符串if(state==end) break;//求'x'的坐标int x,y;for(int i=0;i<9;i++)if(state[i]=='x')x=i/3,y=i%3;//提前存储该状态,需要反复用到string source=state;//枚举四个方向for(int i=0;i<4;i++){int a=x+dx[i],b=y+dy[i];//超出边界if(a<0||a>=3||b<0||b>=3) continue;//原状态赋值state=source;//该状态扩展的新状态swap(state[x*3+y],state[a*3+b]);//该状态之前未出现过或此路径比之前到达该状态的距离更小if(!dist.count(state)||dist[source]+1<dist[state]){//更新扩展出来的新状态的距离dist[state]=dist[source]+1;//记录到达此状态的操作和上一步状态pre[state]={op[i],source};//放入队列,记录起点到终点的估价距离和新状态heap.push({dist[state]+f(state),state});}}}//回推路径string res;while(end!=start){res+=pre[end].x;end=pre[end].y;}reverse(res.begin(),res.end());return res;
}int main()
{string start,seq;char c;while(cin>>c){start+=c;if(c!='x') seq+=c;}int cnt=0;for(int i=0;i<9;i++)for(int j=i+1;j<9;j++)if(seq[i]>seq[j])cnt++;if(cnt&1) puts("unsolvable");else cout<<bfs(start)<<endl;return 0;
}

2.8 DFS之连通性模型(0/2)

2.9 DFS之搜索顺序(0/3)

2.10 DFS之剪枝与优化(0/4)

2.11 迭代加深(0/1)

2.12 双向DFS(0/1)

2.13 IDA*(0/2)

3. 图论(0/58)

3.1 单源最短路的建图方式(0/6)

3.1.1 AcWing 1129. 热浪

3.1.2 AcWing 1128. 信使

3.1.3 AcWing 1127. 香甜的黄油

3.1.4 AcWing 1126. 最小花费

3.1.5 AcWing 920. 最优乘车

3.1.6 AcWing 903. 昂贵的聘礼

AcWing算法提高课相关推荐

  1. ACWing算法提高课 友好城市

    ACWing算法提高课 友好城市 Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市. 北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同. 每 ...

  2. AcWing算法提高课-3.1.2信使

    宣传一下算法提高课整理 <- CSDN个人主页:更好的阅读体验 <- 题目传送门点这里 题目描述 战争时期,前线有 n n n 个哨所,每个哨所可能会与其他若干个哨所之间有通信联系. 信使 ...

  3. AcWing算法提高课-3.1.1热浪

    宣传一下算法提高课整理 <- CSDN个人主页:更好的阅读体验 <- 题目传送门点这里 题目描述 德克萨斯纯朴的民众们这个夏天正在遭受巨大的热浪!!! 他们的德克萨斯长角牛吃起来不错,可是 ...

  4. 算法——AcWing算法提高课中代码和题解

    文章目录 第一章 动态规划 (完成情况:64/68) 数字三角形模型 最长上升子序列模型 背包模型 状态机模型 状态压缩DP 区间DP 树形DP 数位DP 单调队列优化DP 斜率优化DP 第二章 搜索 ...

  5. [AcWing算法提高课]之图论 单源最短路的综合应用(C++题解)

    目录 1)热浪(板子题) (朴素dijkstra) O(n2) (堆优化dijkstra) O((n+m)logm) (spfa) O(m) 2)信使 3)香甜的黄油 4)最小花费 5) 最优乘车 6 ...

  6. AcWing算法提高课笔记

    目录 Level2 1.动态规划--从集合角度考虑DP问题 1.1 数字三角形模型 1.1.1摘花生 1.1.2最低通行费 1.1.3方格取数 1.1.4传纸条 1.2 最长上升子序列模型 1.2.1 ...

  7. AcWing算法提高课 Level-3 第四章 高级数据结构

    并查集 1250. 格子游戏 并查集解决的是连通性(无向图联通分量)和传递性(家谱关系)问题,并且可以动态的维护.抛开格子不看,任意一个图中,增加一条边形成环当且仅当这条边连接的两点已经联通,于是可以 ...

  8. AcWing算法提高课 Level-3 第三章 图论

    单源最短路的建图方式 1129. 热浪 思路 :单源最短路算法中除了bellmanford一般不用以外,普D为O(n2)O(n^2)O(n2),优D为O(m∗logn)O(m*logn)O(m∗log ...

  9. AcWing算法提高课 Level-3 第二章 搜索

    池塘计数 题目 提交记录 讨论 题解 视频讲解 农夫约翰有一片 N∗M 的矩形土地. 最近,由于降雨的原因,部分土地被水淹没了. 现在用一个字符矩阵来表示他的土地. 每个单元格内,如果包含雨水,则用& ...

  10. DP 状态机模型 AcWing算法提高课 详解

    状态机模型 AcWing 1049. 大盗阿福 #include <iostream> #include <algorithm> #include <cmath> ...

最新文章

  1. 【数据结构】二叉树及其相关操作
  2. 一种zabbix server扩容改造方案
  3. 各种好用的代码生成器(C#)
  4. java web开发技巧_java web开发技巧
  5. android手机解除root,手机显示被root什么意思(手机root怎么解除)
  6. 拯救者linux无法正常关机,Ubuntu无法关机解决办法
  7. AndroidStudio_安卓原生开发_拍照存储在Uri中_利用图片后通过Uri获取文件真实路径_然后删除---Android原生开发工作笔记161
  8. 超过 1 亿 Android 用户的数据遭泄露!
  9. 计算机组成原理mw,计算机组成原理 存储器
  10. 各大浏览器兼容性报告
  11. 网线分类及如何选择?
  12. 用户画像数据建模方法
  13. STM32——电容触摸按键
  14. Oracle RMAN无法删除归档一例
  15. 快手上的音乐计算机,快手本地音乐显示只能从电脑导入怎么办
  16. SkeyeVSS综合安防视频云服务无插件WEB直播方案中实现抓取快照功能
  17. 科达出征珠海航展,共筑蓝天梦想
  18. 有4个圆塔、圆心分别为(2,2)、(-2,2)、(-2,-2)、(2,-2),圆半径为1,见图4.5。这4个塔的高度为10m,塔以外无建筑物。今输入任一点的坐标,求该点的建筑高度(塔外的高度为零)
  19. oracle原销售订单退货,取消销售订单
  20. PAC(Probably Approximately Correct,概率近似正确)

热门文章

  1. Java熟食包点系统实战
  2. lol祖安服务器维护,听说坑货都去玩LOL无限火力了?这些英雄终于可以拿出来秀啦!...
  3. stata学习笔记(一)stata入门与基本操作
  4. z时代,汽车品牌如何玩转年轻化营销?
  5. mathjax 渲染LaTeX 数学公式 使用教程
  6. 普通程序猿 文艺程序猿 2B程序猿
  7. “三巨头”新格局——TAB是怎么取代BAT的?
  8. 域名购买 估价与域名的备案
  9. P1广州前端求职的第一个月
  10. DB2 57011 错误