zoukankan      html  css  js  c++  java
  • P1004 方格取数——奇怪的dp

    P1004 方格取数

    题目描述

    设有 (N imes N) 的方格图 ((Nleq 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 imes 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;
    }
    
  • 相关阅读:
    缓存问题
    基情探测器心得
    新手最常见的误解和错误
    C语言书籍推荐
    初学者编程实战指南 (4) 由一个简单的例子学习抽象
    数据结构的动画演示
    利用IDE使你的代码风格好看一些
    初学者编程实战指南 (2) 避免逻辑的重复
    入门编程语言的选择问题
    关于ACM集训队
  • 原文地址:https://www.cnblogs.com/Rubyonly233/p/13221421.html
Copyright © 2011-2022 走看看