P1004-DP【绿】

发布时间 2023-12-09 13:01:41作者: Kai-G

这道题很有趣,暴搜的时间复杂度太过于凶残O(K*(2^n)^2)(K的意思是大常数),不过作为提高组T4,这道题数据范围太小了,感觉哪怕是离谱的暴搜也能过。
再加上一时半会没想好多项式时间复杂度的正解DP,就搞了一个四不像出来,第一次走用搜索来实现第二次走用记搜来实现,这样时间复杂度就是O((2^n)*(n^2)),仍然很凶残但毕竟数据太水了。这个做法很简单,对于现在的我来说简直是基本功了,五十行代码很快便写好然后轻松调了调就AC了

但显然我还是要想想正解的,初步想了一下没想出来便看了眼题解,没看懂题解的思路讲解但是看了眼代码后,瞬间想到了以下内容便理解了整个过程。
此前考虑到第一次搜索会影响第二次搜索而第一次搜索后的结果用状态来描述就要用n个坐标这会导致dp的时间复杂度变为n^n从而使得dp毫无意义。
但是,后来意识到如果同步处理两次移动,用一个四个坐标(即两人坐标)的dp来刻画一个状态,这是他们走的第几步即他们所在那一圈这两个数据显然都隐含在坐标中而且由于状态转移过程中不会改变两人总步数的差且结尾状态两人总步数的差是0!!!这就导致我需要求的那部分的dp两人总步数的差始终为0!!!也就是说两人始终走过总路程相同,他们在同一条“带”上,这意味着根本不需要存某个人走过了哪些步来一一对照,只需要在每一步考虑他们是否在同一带上选择了同一格即可!如果选择了同一格,那去掉重复加了的得分即可。
甚至,由于两人总路程相同,而总路程+两人x坐标可以推导出两人y坐标,所以用总路程+两个x坐标就可以足以刻画状态,就这般,三维DP即可解决问题!
妙哉
思路已经完全清晰,没什么可说的了,也没什么浪费时间写出来的必要了,毕竟想明白以上内容后想写简直太简单了,在这里复制一段别人写的四维dp代码和三维dp代码,仅供参考。

O((2^n)*(n^2))-Code

#include <iostream>
#include <cstring>

using namespace std;
int ans,dp[10][10],N,x,y,z,a[10][10],b[10][10];
int dpdfs(int x,int y)
{
	if(x<1||y<1)return -99999999;
	if(x==1&&y==1)return dp[1][1]=0;
	if(dp[x][y]!=-1)return dp[x][y];
	int DFS=max(dpdfs(x-1,y),dpdfs(x,y-1));
	if(b[x][y]==0)DFS+=a[x][y];
	return dp[x][y]=DFS;
}
void dfs(int x,int y,int l)
{
	if(x>N||y>N)return;
	if(x==N&&y==N)
	{
		memset(dp,-1,sizeof(dp));
		ans=max(ans,l+dpdfs(N,N));
		return;
	}
	b[x+1][y]=1;dfs(x+1,y,l+a[x+1][y]);b[x+1][y]=0;
	b[x][y+1]=1;dfs(x,y+1,l+a[x][y+1]);b[x][y+1]=0;
	return;
}

int main()
{
	cin>>N;
	while(cin>>x>>y>>z)
	{
		if(x==0&&y==0&&z==0) break;
		a[x][y]=z;
	}
	b[1][1]=1;
	dfs(1,1,a[1][1]);
	cout<<ans<<endl;
	return 0;
}

O(n^4)-Code

#include<bits/stdc++.h>
using namespace std;
const int maxn=10;
int a[maxn][maxn],f[maxn<<1][maxn][maxn],n;
int main()
{
	scanf("%d",&n);
	memset(a,0,sizeof(a));
	while (1)
	{
		int x,y,z;
		scanf("%d %d %d",&x,&y,&z);
		if (x==0 && y==0 && z==0) break;
		a[x][y]=z;
	}
	f[0][1][1]=a[1][1];//初始化 
	for (int i=1;i<=2*n-2;i++)//因为最多走2n-2步,x1+x2=i+2 
		for (int x1=1;x1<=n;x1++)
			for (int x2=1;x2<=n;x2++)
			{
				int y1=i+2-x1,y2=i+2-x2;//算出纵坐标 
				if (y1<1 || y2<1) continue;//判断是否越界 
				f[i][x1][x2]=max(f[i-1][x1][x2],max(f[i-1][x1-1][x2],max(f[i-1][x1][x2-1],f[i-1][x1-1][x2-1])))+a[x1][y1]+a[x2][y2];//上面说的转移 
				if (x1==x2 && y1==y2) f[i][x1][x2]-=a[x1][y1];	//如果走到同一个点就减一次 
			}
	printf("%d",f[n*2-2][n][n]);//目标状态 
	return 0;
}

O(n^3)-Code

#include<bits/stdc++.h>
using namespace std;
const int maxn=10;
int a[maxn][maxn],f[maxn<<1][maxn][maxn],n;
int main()
{
	scanf("%d",&n);
	memset(a,0,sizeof(a));
	while (1)
	{
		int x,y,z;
		scanf("%d %d %d",&x,&y,&z);
		if (x==0 && y==0 && z==0) break;
		a[x][y]=z;
	}
	f[0][1][1]=a[1][1];//初始化 
	for (int i=1;i<=2*n-2;i++)//因为最多走2n-2步,x1+x2=i+2 
		for (int x1=1;x1<=n;x1++)
			for (int x2=1;x2<=n;x2++)
			{
				int y1=i+2-x1,y2=i+2-x2;//算出纵坐标 
				if (y1<1 || y2<1) continue;//判断是否越界 
				f[i][x1][x2]=max(f[i-1][x1][x2],max(f[i-1][x1-1][x2],max(f[i-1][x1][x2-1],f[i-1][x1-1][x2-1])))+a[x1][y1]+a[x2][y2];//上面说的转移 
				if (x1==x2 && y1==y2) f[i][x1][x2]-=a[x1][y1];	//如果走到同一个点就减一次 
			}
	printf("%d",f[n*2-2][n][n]);//目标状态 
	return 0;
}

以上、