2025.2.17-2025.2.18 做题记录
题目内容为:多维dp
感觉多维dp的思路都好懂但是想不到。总是想到一些复杂度不对或者实现及其复杂的方法,求救。
子串
感觉像正常的字符串比较额外加一个条件限制 \(k\) 。考虑正常的字符串比较做法,额外添加一维状态为 \(k\) 即可。
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
#define int long long
using namespace std;
const int N=2e3+50;
const int mod=1000000007;
int n,m,k;
char a[N],b[N];
int dp[3][N][N][3];
signed main(){
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=m;i++){
cin>>b[i];
}
dp[0][0][0][0]=1;
dp[1][0][0][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
for(int p=1;p<=k;p++){
if(a[i]==b[j]){
dp[i%2][j][p][1]=dp[(i-1)%2][j-1][p][1]%mod+dp[(i-1)%2][j-1][p-1][1]%mod+dp[(i-1)%2][j-1][p-1][0]%mod;
dp[i%2][j][p][1]%=mod;
dp[i%2][j][p][0]=dp[(i-1)%2][j][p][0]%mod+dp[(i-1)%2][j][p][1]%mod;
dp[i%2][j][p][0]%=mod;
}else{
dp[i%2][j][p][1]=0;
dp[i%2][j][p][0]=dp[(i-1)%2][j][p][0]%mod+dp[(i-1)%2][j][p][1]%mod;
dp[i%2][j][p][0]%=mod;
}
}
}
}
cout<<(dp[(n)%2][m][k][0]%mod+dp[(n)%2][m][k][1]%mod)%mod;
return 0;
}
传纸条
首先观察到来回不能重复,可以认为从左上角出发,到右下角选择两条不交叉的路径使得两条路径和最大,而且这两条路一定以对角线为界,两侧分开。dp 数组设置为两条路的坐标,注意细节即可。
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=60;
int n,m;
int a[N][N];
int f[N][N][N][N];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
}
for(int x1=1;x1<=n;x1++){
for(int y1=1;y1<=m;y1++){
for(int x2=x1+1;x2<=n;x2++){
for(int y2=1;y2<y1;y2++){
f[x1][y1][x2][y2]=max(f[x1-1][y1][x2-1][y2],max(f[x1][y1-1][x2-1][y2],max(f[x1-1][y1][x2][y2-1],f[x1][y1-1][x2][y2-1])))+a[x1][y1]+a[x2][y2];
}
}
}
}
cout<<f[n-1][m][n][m-1];
return 0;
}
多米诺骨牌
上来率先考虑定义 dp 数组的 \(i,j\) 为上,下的和,最后便利数组寻找最优解。发现复杂度不对,寄。将 \(i,j\) 重新定义为对于前 \(i\) 个多米诺骨牌,使得差值为 \(j\) 的最小操作次数。然后就可以死磕哩。
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=1e3+50;
int n;
int a[N],b[N];
int f[N][12*N];
int ans=-1;
const int M=6000;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i]>>b[i];
}
memset(f,0x3f,sizeof(f));
f[0][0+M]=0;
for(int i=1;i<=n;i++){
for(int j=-5000;j<=5000;j++){
int dis=a[i]-b[i];
// 不反转 反转
f[i][j+M]=min(f[i-1][j-dis+M],f[i-1][j+dis+M]+1);
}
}
for(int i=0;i<=5000;i++){
ans=min(f[n][i+M],f[n][-i+M]);
if(ans<=1000){
cout<<ans;
return 0;
}
}
return 0;
}
Corn Fields G
人生第一道状压dp题。感觉这道题的思路非常优美且自然,可以当作状压dp的入门题。
考虑将每一行合法的情况与图的实际情况转为 01 串后再转为十进制数。那么对于一行的情况是否合法只需要判断 g[j]==true 与 F[j] & j == j 即可。详细见代码注释。
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
#define int long long
using namespace std;
const int p=100000000;
int n,m,ans;
int mp[15][15];
int F[15],f[15][(1<<12)+50];
int g[(1<<12)];
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>mp[i][j];
}
}
//将图的每一行压到一维
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
F[i]=(F[i]<<1)+mp[i][j];
}
}
//预处理第一行
for(int i=0;i<(1<<m);i++){
if(!(i&(i<<1)) && !(i&(i>>1))){
g[i]=1;
if((i&F[1])==i) f[1][i]=1;
}
}
for(int i=2;i<=n;i++){
for(int j=0;j<(1<<m);j++){
//如果 i-1 行能存在 j 状态,并且不矛盾。
if((j&F[i-1])==j && g[j]){
for(int k=0;k<(1<<m);k++){
//如果第 i 行能存在 k 状态,并且不矛盾。
//g[k]表示左右不矛盾,j&k表示上下不矛盾。
if((k&F[i])==k && g[k] && !(j&k)){
f[i][k]=(f[i][k]%p+f[i-1][j]%p)%p;
}
}
}
}
}
for(int i=0;i<(1<<m);i++){
ans=(ans%p+f[n][i]%p)%p;
}
cout<<ans;
return 0;
}
互不侵犯
同上为状压dp题。我才不会说某个唐诗er把dp数组定义为每一行在 \(j\) 情况下这一行放了 \(p\) 个国王而非整张图已经放了 \(p\) 个国王,怒调一个小时无果后开题解也没看明白的事。
好了上面我已经把dp数组说完了,接下来考虑细节。细节同上一道题,好了我说完了。
#include <bits/stdc++.h>
using namespace std;
int n, t;
int F[(1 << 10)];
long long f[10][(1 << 10)][101];
long long ans = 0;
int main() {
cin >> n >> t;
for (int i = 0; i < (1 << n); i++) {
if (!(i & (i << 1)) && !(i & (i >> 1))) {
F[i] = 1;
int p = __builtin_popcount(i);
if (p <= t) {
f[1][i][p] = 1;
}
}
}
for (int i = 2; i <= n; i++) {
for (int j = 0; j < (1 << n); j++) {
if (F[j]) {
for (int k = 0; k < (1 << n); k++) {
int q = __builtin_popcount(k);
if (F[k] && !(j & k) && !(j & (k << 1)) && !(j & (k >> 1))) {
for(int pp=t;pp>=q;pp--){
f[i][k][pp]+=f[i-1][j][pp-q];
}
}
}
}
}
}
for (int i = 0; i < (1 << n); i++) {
ans += f[n][i][t];
}
cout << ans;
return 0;
}
跳舞
dp 数组的 \(i,j\) 表示总共跳 \(i\) 步,跳了 \(j\) 次时的情况,剩下的就是一个基本的背包+额外的判定。
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int N=5e3+50;
int n,t,ans=-1;
int s[N],b[N];
int f[N][N];
signed main(){
cin>>n>>t;
for(int i=1;i<=n;i++){
cin>>s[i];
}
for(int i=1;i<=n;i++){
cin>>b[i];
}
for(int i=1;i<=n;i++){
f[i][0]=f[i-1][0]-s[i];
}
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
if(j%t!=0)
f[i][j]=max(f[i-1][j-1]+s[i],f[i-1][j]-s[i]);
else
f[i][j]=max(f[i-1][j-1]+s[i]+b[i],f[i-1][j]-s[i]);
}
}
for(int i=1;i<=n;i++){
ans=max(ans,f[n][i]);
}
cout<<ans;
return 0;
}
也不知道为啥想半天没想到。
小a和uim大逃离
还是比较简单的,两个人交替向右或向下走,拿到魔夜大于 \(k\) 时记得清空。定义 dp 数组为 坐标 \(i,j\) 时,手里有 \(k\) 的魔夜,是 \(1/0\) 走到这里的,共四维。转移就正常转移就行。统计答案时只需要统计魔夜为 \(0\) 且是 uim 结束的情况
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int mod=1000000007;
int n,m,k;
int mp[805][805];
int f[805][805][20][2];
int main(){
cin>>n>>m>>k;
k++;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>mp[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
f[i][j][mp[i][j]%k][0]=1;
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
for(int p=0;p<k;p++){
f[i][j][p][0]=(f[i][j][p][0]+f[i-1][j][(p-mp[i][j]+k)%k][1])%mod;
f[i][j][p][0]=(f[i][j][p][0]+f[i][j-1][(p-mp[i][j]+k)%k][1])%mod;
f[i][j][p][1]=(f[i][j][p][1]+f[i-1][j][(p+mp[i][j])%k][0])%mod;
f[i][j][p][1]=(f[i][j][p][1]+f[i][j-1][(p+mp[i][j])%k][0])%mod;
}
}
}
long long ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
ans=(ans%mod+f[i][j][0][1]%mod)%mod;
}
}
cout<<ans;
return 0;
}
垃圾陷阱
我真想不到这么定义dp数组:\(dp_i\) 表示在 \(i\) 高度生存的最长时间,然后直接做即可。详见注释。
#include<iostream>
#include<queue>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
int d,g;
// f[i] 表示在 i 高度上存活的最长时间 f[i]
int f[110];
struct node{
int t,f,h;
}a[110];
bool cmp(node a,node b){
return a.t<b.t;
}
int main(){
cin>>d>>g;
for(int i=1;i<=g;i++){
cin>>a[i].t>>a[i].f>>a[i].h;
}
sort(a+1,a+1+g,cmp);
f[0]=10;
for(int i=1;i<=g;i++){
for(int j=d;j>=0;j--){
//如果能活到垃圾掉下来
if(f[j]>=a[i].t){
//如果能直接走
if(j+a[i].h>=d){
cout<<a[i].t;
return 0;
}
//拿来垫脚
f[j+a[i].h]=max(f[j+a[i].h],f[j]);
//拿来吃
f[j]+=a[i].f;
}
}
}
cout<<f[0];
return 0;
}

浙公网安备 33010602011771号