AcWing算法提高课
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) 走到 (i,j)(i,j)(i,j) 的所有路线
属性:max\maxmax - 状态转移
从 (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] - 滚动数组
由于 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) 走到 (i,j)(i,j)(i,j) 的所有路线
属性:min\minmin - 状态转移
从 (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] - 滚动数组
由于 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) 走到 (i1,j1),(i2,j2)(i1,j1),(i2,j2)(i1,j1),(i2,j2) 的所有路线
属性:max\maxmax - 状态转移
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. 怪盗基德的滑翔翼
思路:
- 状态表示
集合:所有以 hih_ihi 结尾的上升子序列
属性:max\maxmax - 状态转移
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) - 正向和反向各求解一遍 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. 最大上升子序列和
思路:
- 状态表示
集合:所有以 aia_iai 为结尾的严格上升子序列的和
属性:max\maxmax - 状态转移
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. 拦截导弹
结论: 第一问求最长下降子序列,第二问求最长上升子序列
思路:
- 如果现有子序列的结尾数都小于当前数,那就创建一个新的序列
- 否则,就将当前数放在现有子序列的大于等于当前数的最小结尾数的后面
- 因此我们可以巧妙地发现,他们的结尾数呈一个最长上升子序列的形态
Example:
- 母序列:389 207 155 300 299 170 158 65
- 子序列1:389 207 155 65
- 子序列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. 导弹防御系统
思路:
- 本题就是问最少需要多少个最长上升子序列和最长下降子序列
- up 数组存所有上升子序列的结尾数,down 数组存所有下降子序列的结尾数,ans 全局变量存最少子序列的个数
- 直接用 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])
思路:
- 状态表示
集合:从前 iii 个物品中选,体积不超过 jjj 的价值
属性:max\maxmax - 状态转移
不选第 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] - 滚动数组
由于 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)
思路:
- 状态表示
集合:从前 iii 个物品中选,费用111 不超过 jjj,费用222 不超过 kkk 的价值
属性:max\maxmax - 状态转移
不选第 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 - 滚动数组
由于 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]
思路:
- 状态表示
集合:从前 iii 个物品中选,体积等于 jjj 的方案
属性:CountCountCount - 状态转移
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] - 滚动数组
由于 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]
思路:
- 状态表示
集合:从前 iii 个物品中选,体积等于 jjj 的方案
属性:CountCountCount - 状态转移
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,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]
思路:
- 状态表示
集合:从前 iii 个物品中选,体积等于 jjj 的方案
属性:CountCountCount - 状态转移
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,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. 货币系统
思路:
- 易发现 b[i]b[i]b[i] 一定是从 a[1~n]a[1 ~ n]a[1~n] 中无法由其他数字构成的数字
- 用完全背包判断该数字是否可有其他数字组成,如果不可以则记录
代码:
#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])
思路:
- 状态表示
集合:从前 iii 个物品中选,体积不超过 jjj 的价值
属性:max\maxmax - 状态转移
第 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]) - 滚动数组
由于 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])
思路:
- 状态表示
集合:只从前 iii 个物品中选,体积不超过 jjj,重量不超过 kkk 的价值
属性:max\maxmax - 状态转移
不选第 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] - 滚动数组
由于 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)
思路:
- 状态表示
集合:只从前 iii 个物品中选,氧气至少是 jjj,氮气至少是 kkk 的重量
属性:min\minmin - 状态转移
不选第 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 - 滚动数组
由于 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. 机器分配
思路:
- 状态表示
集合:只从前 iii 组物品中选,体积不超过 jjj 的价值
属性:max\maxmax - 状态转移
选择第 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])
思路:
- 状态表示
集合:只从前 iii 个物品中选,体积不超过 jjj 的价值
属性:max\maxmax - 状态转移
不选第 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] - 滚动数组
由于 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. 背包问题求方案数
思路:
- 如果 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](可省略)
- 如果 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]
- 如果 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. 背包问题求具体方案
思路:
- 状态表示
集合:从第 i∼ni\sim ni∼n 个物品中选,体积不超过 jjj 的价值
属性:max\maxmax - 状态转移
不选第 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]
本题思路:
- 如果 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 个物品
- 如果 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 个物品
- 如果 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 个物品
- 总结:只要 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. 金明的预算方案
思路:
- 将第 iii 个主件以及其携带的附件看成是第 iii 组物品
- 假设第 iii 个主件有 nnn 个附件,则第 iii 组物品有 2n2 ^ n2n 种决策
- 依次枚举每组物品,体积,决策
代码:
#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. 大盗阿福
思路:
- 状态表示
集合:只从前 iii 个物品中选,f[i][0]f[i][0]f[i][0] 表示不选第 iii 个物品,f[i][1]f[i][1]f[i][1] 表示选择第 iii 个物品
属性:max\maxmax - 状态转移
不选第 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
思路:
- 状态表示
集合:只从前 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] 表示手中有货 - 状态转移
手中无货: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]) - 优化
手中无货: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]) - 提醒
一次完整的买入卖出才算一次交易的真正结束
代码:
#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
思路:
- 状态表示
集合:从前 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 - 状态转移
手中有货: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. 小国王
思路:
- 状态表示
集合:前 iii 行已摆好,且已摆放好 jjj 个国王,第 iii 行的状态是 aaa 的方案
属性:CountCountCount - 状态转移
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. 玉米田
思路:
- 状态表示
集合:前i行已摆好,且第i行的状态是a的方案
属性:CountCountCount - 状态转移
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. 环形石子合并
思路:
- 状态表示
集合:区间 [l,r][l,r][l,r] 内的石子合并后的得分
属性:min\minmin - 状态转移
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) - 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. 能量项链
思路:
- 状态表示
集合:区间[l,r][l,r][l,r]内的珠子合并后的得分
属性:max\maxmax - 状态转移
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) - 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. 加分二叉树
思路:
- 状态表示
集合:区间 [l,r][l,r][l,r] 内的节点合并后可以得到的分数
属性:max\maxmax - 状态转移
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. 凸多边形的划分
思路:
- 状态表示
集合:区间 [l,r][l,r][l,r] 内的顶点划分三角形后各权值乘积之和
属性:min\minmin - 状态转移
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) - 为什么不用断环成链?
因为划分三角形后,凸多边形的所有边都一定是某个三角形的一边,即每条边都一定存在
代码:
#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. 树的最长路径
思路:
- 随便找一个点 aaa,从点 aaa 出发找到能到达的最远的点 bbb,再从点 bbb 出发找到能到达的最远的点 ccc,则 bcbcbc 为直径
- 枚举所有的根结点,找出以根结点为顶点的最大长度的最大值 ansansans
- 对于每个根结点,往下找最大距离 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. 树的中心
思路:
- d1[u]d1[u]d1[u] 表示从 uuu 往下走能到达的最大路径,d2[u]d2[u]d2[u] 表示从 uuu 往下走能到达的次大路径
- p1[u]p1[u]p1[u] 表示从 uuu 出发的最大路径需要经过 uuu 的儿子节点 jjj,up[u]up[u]up[u] 表示从 uuu 往上走能到达的最大路径
- jjj 往上走到父节点 uuu,从 uuu 可以往上走,也可以往下走不经过 jjj 的路径
- 枚举所有点,找出该点能到达的最远距离 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. 数字转换
思路:
- 从倍数角度出发,让 iii 的倍数的 sum[i⋅j]sum[i \cdot j]sum[i⋅j] 加上 iii
- 每个 iii 只会有一个 sum[i]sum[i]sum[i],即 sum[i]sum[i]sum[i] 是 iii 的父节点
- 最后就是求一个树的直径
代码:
#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. 池塘计数
思路:
- 枚举所有点,符合条件就 dfsdfsdfs 该点
- 将该点变成其他不符合条件的符号,这样就不用 ststst 数组来记录了
- 枚举八个方向,符合条件的点就直接 dfsdfsdfs
- 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. 城堡问题
思路:
- cntcntcnt 表示连通块个数,areaareaarea 表示连通块最大面积
- 依次枚举每个点,如果该点没被记录过,就将其入队并记录该点
- 取出队头,area++area++area++
- 枚举四个方向,左西,上北,右东,下南
- 将符合的点入队,并记录该点
- 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. 山峰和山谷
思路:
- 山峰:没有任何山比他高;山谷:没有任何山比他低;故找是否有不符合其条件的点即可
- 枚举所有点,如果该点未被记录,就将其加入队列并记录该点
- 取出队头,枚举该点的八个方向,观察是否有点比他高或比他低
- 如果高度相等并且该点未被记录过,就加入队列并记录该点
- 等所有点枚举完后,山峰和山谷的数量就出来了
代码:
#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. 迷宫问题
思路:
- 因为要正向记录路径,所以 bfsbfsbfs 可以反向搜
- preprepre 数组不仅可以记录该点上一步状态,还能判断该点是否走过
- 取出队头,枚举四个方向,符合条件的加入队列,并记录上一步状态
- 最后从 (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. 武士风度的牛
思路:
- 找到 KKK 的坐标,对其做 bfsbfsbfs
- 初始化队列 qqq 和 distdistdist 数组,并把该点放入队列和更新距离
- 枚举日子格八个方向,将符合条件的点放入队列并更新距离
- 如果找到终点 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. 抓住那头牛
思路:
- 农民所走的点的范围:[0,N)[0, N)[0,N)
- 找到起点放入队列,并更新起点步数
- 三种操作:t+1,t−1,t×2t + 1,t - 1,t \times 2t+1,t−1,t×2
- 符合范围且该点未走过,则放入队列并更新步数
- 到达终点 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. 矩阵距离
思路:
- 将所有起点放入队列,并将他们的距离置为 000
- 取出队头,枚举四个方向,将符合条件的点放入队列,并更新距离
代码:
#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. 魔板
思路:
- 跟八数码一样都是求最小步数,需要状态转换
- distdistdist 需要记录到达该状态需要的步数,preprepre 需要记录到达该状态的操作和上一步状态
- 枚举三种状态,如果之前没有出现过,就更新 distdistdist 和 preprepre
- 如果到达目标状态,就直接返回,否则,放入队列
代码:
#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算法提高课相关推荐
- ACWing算法提高课 友好城市
ACWing算法提高课 友好城市 Palmia国有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市. 北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同. 每 ...
- AcWing算法提高课-3.1.2信使
宣传一下算法提高课整理 <- CSDN个人主页:更好的阅读体验 <- 题目传送门点这里 题目描述 战争时期,前线有 n n n 个哨所,每个哨所可能会与其他若干个哨所之间有通信联系. 信使 ...
- AcWing算法提高课-3.1.1热浪
宣传一下算法提高课整理 <- CSDN个人主页:更好的阅读体验 <- 题目传送门点这里 题目描述 德克萨斯纯朴的民众们这个夏天正在遭受巨大的热浪!!! 他们的德克萨斯长角牛吃起来不错,可是 ...
- 算法——AcWing算法提高课中代码和题解
文章目录 第一章 动态规划 (完成情况:64/68) 数字三角形模型 最长上升子序列模型 背包模型 状态机模型 状态压缩DP 区间DP 树形DP 数位DP 单调队列优化DP 斜率优化DP 第二章 搜索 ...
- [AcWing算法提高课]之图论 单源最短路的综合应用(C++题解)
目录 1)热浪(板子题) (朴素dijkstra) O(n2) (堆优化dijkstra) O((n+m)logm) (spfa) O(m) 2)信使 3)香甜的黄油 4)最小花费 5) 最优乘车 6 ...
- AcWing算法提高课笔记
目录 Level2 1.动态规划--从集合角度考虑DP问题 1.1 数字三角形模型 1.1.1摘花生 1.1.2最低通行费 1.1.3方格取数 1.1.4传纸条 1.2 最长上升子序列模型 1.2.1 ...
- AcWing算法提高课 Level-3 第四章 高级数据结构
并查集 1250. 格子游戏 并查集解决的是连通性(无向图联通分量)和传递性(家谱关系)问题,并且可以动态的维护.抛开格子不看,任意一个图中,增加一条边形成环当且仅当这条边连接的两点已经联通,于是可以 ...
- AcWing算法提高课 Level-3 第三章 图论
单源最短路的建图方式 1129. 热浪 思路 :单源最短路算法中除了bellmanford一般不用以外,普D为O(n2)O(n^2)O(n2),优D为O(m∗logn)O(m*logn)O(m∗log ...
- AcWing算法提高课 Level-3 第二章 搜索
池塘计数 题目 提交记录 讨论 题解 视频讲解 农夫约翰有一片 N∗M 的矩形土地. 最近,由于降雨的原因,部分土地被水淹没了. 现在用一个字符矩阵来表示他的土地. 每个单元格内,如果包含雨水,则用& ...
- DP 状态机模型 AcWing算法提高课 详解
状态机模型 AcWing 1049. 大盗阿福 #include <iostream> #include <algorithm> #include <cmath> ...
最新文章
- 【数据结构】二叉树及其相关操作
- 一种zabbix server扩容改造方案
- 各种好用的代码生成器(C#)
- java web开发技巧_java web开发技巧
- android手机解除root,手机显示被root什么意思(手机root怎么解除)
- 拯救者linux无法正常关机,Ubuntu无法关机解决办法
- AndroidStudio_安卓原生开发_拍照存储在Uri中_利用图片后通过Uri获取文件真实路径_然后删除---Android原生开发工作笔记161
- 超过 1 亿 Android 用户的数据遭泄露!
- 计算机组成原理mw,计算机组成原理 存储器
- 各大浏览器兼容性报告
- 网线分类及如何选择?
- 用户画像数据建模方法
- STM32——电容触摸按键
- Oracle RMAN无法删除归档一例
- 快手上的音乐计算机,快手本地音乐显示只能从电脑导入怎么办
- SkeyeVSS综合安防视频云服务无插件WEB直播方案中实现抓取快照功能
- 科达出征珠海航展,共筑蓝天梦想
- 有4个圆塔、圆心分别为(2,2)、(-2,2)、(-2,-2)、(2,-2),圆半径为1,见图4.5。这4个塔的高度为10m,塔以外无建筑物。今输入任一点的坐标,求该点的建筑高度(塔外的高度为零)
- oracle原销售订单退货,取消销售订单
- PAC(Probably Approximately Correct,概率近似正确)