P1004 方格取数——奇怪的dp

P1004 方格取数

题目描述

设有 \(N\times N\) 的方格图 \((N\leq 20)\),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字 \(0\) 。如下图所示(见样例):

A
 0  0  0  0  0  0  0  0
 0  0 13  0  0  6  0  0
 0  0  0  0  7  0  0  0
 0  0  0 14  0  0  0  0
 0 21  0  0  0  4  0  0
 0  0 15  0  0  0  0  0
 0 14  0  0  0  0  0  0
 0  0  0  0  0  0  0  0
                         B

某人从图的左上角的 \(A\) 点出发,可以向下行走,也可以向右走,直到到达右下角的 \(B\) 点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字 \(0\) )。

此人从 \(A\) 点到 \(B\) 点共走两次,试找出 \(2\) 条这样的路径,使得取得的数之和为最大。

输入格式

输入的第一行为一个整数 \(N\) (表示 \(N\times N\) 的方格图),接下来的每行有三个整数,前两个表示位置,第三个数为该位置上所放的数。一行单独的 \(0\) 表示输入结束。

输出格式

只需输出一个整数,表示 \(2\) 条路径上取得的最大的和。

输入输出样例

输入

8
2 3 13
2 6  6
3 5  7
4 4 14
5 2 21
5 6  4
6 3 15
7 2 14
0 0  0

输出

67

说明/提示

NOIP 2000 提高组第四题

错误思路

刚开始看题,肯定会想出暴力 \(Dfs\) 进行第一遍,把走过的置为 \(0\) ,然后再 \(Dfs\) 第二遍,将两次结果加和就是答案。(我就是这么错的)

为什么错呢?

首先,记录路径很麻烦,其次有些数据可以正好把 \(Dfs\) 卡掉(如下图),最后是效率太差了。

A
 0  0  2  3  0  0  0
 0  0  3  0  0  0  0
 0  0  3  0  0  0  0
 0  0  0  0  0  0  0
 0  0  0  0  4  0  0
 0  0  0  0  4  0  0
 0  0  2  0  4  0  0
                      B

很明显正确走法是

\((1,1)—>(1,2)—>(1,3)—>(1,4)—>(1,5)—>···—>(5,5)—>(6,5)—>(7,5)—>(7,6)—>(7,7)\)

第二次便可把剩下的都走完。

但是走 \(Dfs\) ,第一次走的是值最大的路径

\((1,1)—>(1,2)—>(1,3)—>(2,3)—>(3,3)—>(4,3)—>(5,3)—>(5,4)—>(5,5)—>(6,5)—>(7,5)—>(7,6)—>(7,7)\)

第二次剩下的 \(2\)\(3\) 只能走一个,就被卡掉了。

暴力错码(Dfs)

#include <bits/stdc++.h>
using namespace std;

int pan[25][25];
int pathx;
int pathy;
int n;
int maxtot;
int ans1;
int ans2;
int ans[25][25];
void dfs1(int x,int y,int cnt,int tot){//走第一遍
	if(x<=0||x>n||y<=0||y>n){
		return;
	}
	if(x==n&&y==n){
		ans1=max(ans1,cnt);
		if(ans1==cnt){
			maxtot=max(maxtot,tot);
		}
		return;
	}
	cnt+=pan[x][y];
	if(pan[x][y]!=0){
		tot++;
	}
	ans[x][y]=max(ans[x][y],cnt);
	dfs1(x+1,y,cnt,tot);
	dfs1(x,y+1,cnt,tot);
}

void dfspath(int x,int y,int cnt,int tot){//路径的记录,效率太差了
	if(x<=0||x>n||y<=0||y>n){
		return;
	}
	if(x==n&&y==n){
		return;
	}
	cnt+=pan[x][y];
	if(pan[x][y]!=0){
		tot++;
		if(ans1==cnt){
			pathx=x;
			pathy=y;
		}
	}
	dfspath(x+1,y,cnt,tot);
	dfspath(x,y+1,cnt,tot);
}
int xsx;
void Find(int x,int y){
	if(x<=0||x>n||y<=0||y>n||xsx==0){
		return;
	}
	if(x==1&&y==1){
		xsx-=pan[x][y];
		pan[x][y]=0;
		return;
	}
	for(int i=1;i<=x;i++){
		for(int j=1;j<=y;j++){
			if(ans[i][j]+pan[x][y]==xsx){
				xsx-=pan[x][y];
				pan[x][y]=0;
				Find(i,j);
				break;
			}
		}
	}
}
void dfs2(int x,int y,int cnt,int tot){//走第二遍
	if(x<=0||x>n||y<=0||y>n){
		return;
	}
	if(x==n&&y==n){
		ans2=max(ans2,cnt);
		return;
	}
	cnt+=pan[x][y];
	dfs2(x+1,y,cnt,tot);
	dfs2(x,y+1,cnt,tot);
}
int main(){
	scanf("%d",&n);
	int x,y,w;
	while(scanf("%d%d%d",&x,&y,&w)==3){
		if(x==0&&y==0&&w==0)break;
		pan[x][y]=w;
	}
	dfs1(1,1,0,0);
	dfspath(1,1,0,0);
	ans[n][n]=ans1;
	xsx=ans1;
	Find(pathx,pathy);
	dfs2(1,1,0,0);
	cout<<ans1+ans2<<endl;
	return 0;
}

正解思路

首先,我们可以把一个人走两遍,看成两个人走一遍,但是当两人走到同一个有分值的地方时,只能加一次改点的值。

所以可以用 \(dp\) 来解决, \(dp[i][j][l][r]\) 表示当第一个人在 \((i,j)\) ,第二个人在 \((l,r)\) 时所取得的最大值。

因为 \(n\) 比较小,撑死了到 \(20\) ,所 \(dp\) 的效率和四维的数组还是可以接受的,毕竟 \(Dfs\)\(4\) 个点就走了 \(4000ms\)

所以将 \(i\)\(j\)\(l\)\(r\) 依次从 \(1\)\(n\) 枚举,取最大值即可,注意判断两个人在同一位置的情况。

代码

#include <bits/stdc++.h>
using namespace std;
int n;
int pan[25][25];//记录每个点的值
int dp[25][25][25][25];
int main(){
    scanf("%d",&n);
    int x,y,w;
    while(scanf("%d%d%d",&x,&y,&w)==3){
	if(x==0&&y==0&&w==0)break;
	pan[x][y]=w;
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            for(int l=1;l<=n;l++){
                for(int r=1;r<=n;r++){
                    if(i==l&&j==r){//若两人在同一位置只用加一次pan[i][j]的值
                        dp[i][j][l][r]=pan[i][j]+max(max(dp[i-1][j][l-1][r],dp[i-1][j][l][r-1]),max(dp[i][j-1][l-1][r],dp[i][j-1][l][r-1]));
                    }else{//两人不在同一点,两个值都加上即可
                        dp[i][j][l][r]=pan[i][j]+pan[l][r]+max(max(dp[i-1][j][l-1][r],dp[i-1][j][l][r-1]),max(dp[i][j-1][l-1][r],dp[i][j-1][l][r-1]));
                    }
                }
            }
        }
    }
    cout<<dp[n][n][n][n]<<endl;
    return 0;
}

优化

还可以把的 \(for\) 循环优化成三维的,利用小人只能向下和向右走的性质,他的横坐标加上纵坐标一定等于时间。

\(dp\) 数组意义同上,利用第一个小人的横纵坐标,减去第二个小人的横坐标,便可把第四层循环省去。也就省去了 \(10ms\)

代码

#include <bits/stdc++.h>
using namespace std;
int n;
int pan[25][25];
int dp[25][25][25][25];
int main(){
    scanf("%d",&n);
	int x,y,w;
	while(scanf("%d%d%d",&x,&y,&w)==3){
		if(x==0&&y==0&&w==0)break;
		pan[x][y]=w;
	}
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            for(int l=1;l<=n;l++){
                int r=i+j-l;//利用i、j求出第二个小人的纵坐标
                if(i==l&&j==r){
                    dp[i][j][l][r]=pan[i][j]+max(max(dp[i-1][j][l-1][r],dp[i-1][j][l][r-1]),max(dp[i][j-1][l-1][r],dp[i][j-1][l][r-1]));
                }else{
                    dp[i][j][l][r]=pan[i][j]+pan[l][r]+max(max(dp[i-1][j][l-1][r],dp[i-1][j][l][r-1]),max(dp[i][j-1][l-1][r],dp[i][j-1][l][r-1]));
                }
            }
        }
    }
    cout<<dp[n][n][n][n]<<endl;
    return 0;
}
posted @ 2020-07-01 20:46  Rubyonlу  阅读(127)  评论(0编辑  收藏  举报