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;
}
posted @ 2025-02-19 19:16  Tighnari  阅读(16)  评论(0)    收藏  举报