luogu P1004 方格取数

题目描述

设有 N×NN imes NN×N 的方格图 (N≤9)(N le 9)(N9),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字 000。如下图所示(见样例):

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

某人从图的左上角的 AAA 点出发,可以向下行走,也可以向右走,直到到达右下角的 BBB 点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字 000)。
此人从 AAA 点到 BBB 点共走两次,试找出 222 条这样的路径,使得取得的数之和为最大。

输入格式

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

输出格式

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

输入输出样例

输入 #1
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
输出 #1
67

说明/提示

NOIP 2000 提高组第四题

第一遍:自己做题时候的思路

当时的做法其实应该称不上是dp,应该说是贪心?每次选择最优的解,记录经过路径,第一次遍历结束之后把跑过的路径值全部清零,再跑第二次,就可以得到最优解(贪心一定了)。

(由于数据太弱,这样的做法可以骗到60分)

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

int n;
int a[10][10];

struct node{
    int last,s,x,y;
}f[20][20];

int ans=0;

int main()
{
    cin>>n;
    int p;
    while(cin>>p&&p!=0){
        int y,z;
        cin>>y>>z;
        a[p][y]=z;
    }
//    cout<<" ****"<<endl;
    
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            f[i][j].s =0;
            f[i][j].last =-1;
        }
        /*
4
2 2 3
2 3 4
3 1 2
4 2 1
        */
    
//    cout<<" ****"<<endl;
    
    
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            if(f[i-1][j].s +a[i][j]>f[i][j-1].s +a[i][j])
            {
                f[i][j].s =f[i-1][j].s +a[i][j];
                f[i][j].last =a[i-1][j];
                f[i][j].x =i-1;
                f[i][j].y =j; 
            }
            else
            {
                f[i][j].s =f[i][j-1].s +a[i][j];
                f[i][j].last =a[i][j-1];
                f[i][j].x =i;
                f[i][j].y =j-1;
            }
        }
        
    f[1][1].last =-1;
        
//    cout<<f[1][1].last <<endl;
//    cout<<" ****"<<endl;
    ans=f[n][n].s ;
    
    node now=f[n][n];
    int x=f[n][n].x ;
    int y=f[n][n].y ;
//    cout<<" ****"<<endl;
    while(now.last!=-1&&x!=0&&y!=0)
    {
//        cout<<x<<" "<<y<<endl;
        a[x][y]=0;
        now=f[x][y];
        x=now.x ;
        y=now.y ;
    }
//    cout<<" ****5"<<endl;
    
    a[now.x ][now.y ]=0;
    
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            f[i][j].s =0;
            f[i][j].last =-1;
        }
    
    
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++){
            if(f[i-1][j].s +a[i][j]>f[i][j-1].s +a[i][j])
            {
                f[i][j].s =f[i-1][j].s +a[i][j];
                f[i][j].last =a[i-1][j];
                f[i][j].x =i-1;
                f[i][j].y =j; 
            }
            else
            {
                f[i][j].s =f[i][j-1].s +a[i][j];
                f[i][j].last =a[i][j-1];
                f[i][j].x =i;
                f[i][j].y =j-1;
            }
        }
        
    ans+=f[n][n].s ;
    
    cout<<ans<<endl;
    return 0;
    
}

但是我们仔细想一想就会发现,如果我们只是贪心求解的话,是存在情况不满足这种贪心方式的,比如一开始,我们求出了最长路径,并且成功把最长路径清零了,但是下一次我们就会发现我们有可能把完整路径破坏掉了!就是说一开始我们本可以采取其他的路径走法,使得权值的所有点都可以被遍历到,但是我们人为破坏了这样的路径,最终自然得不到最大路径了。

第二遍:正解(n^4)

我们来考虑正解,其实我们发现,题目中总共存在四个状态量。我们完全可以把题目想成这样:有两个人同时走,从起点到达终点,问这两个人的所走路径和最大是多少,我们不妨先将所有状态都设置出来,如f[i][j][k][l]表示第一个人所在位置是(i,j),第二个人所在位置是(k,l),这样的话,f的意思表示为第一个人在(i,j)的位置,第二个人在(k,l)的位置时所经过的最大路径之和,那么f[n][n][n][n]就是我们最后要求的答案

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

int n;
struct node{
    int a,b,c;
}s[502];
int mapa[502][502];
int f[20][20][20][20];

int main()
{
    int p=0;
    cin>>n;
    while(1)
    {
        p++;
        cin>>s[p].a >>s[p].b >>s[p].c ;
        if(s[p].a ==0&&s[p].b ==0&&s[p].c ==0) break;
    }
    
    for(int i=1;i<=p;i++)
    {
        mapa[s[i].a ][s[i].b ]=s[i].c ;
    }
    
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            for(int k=1;k<=n;k++)
                for(int l=1;l<=n;l++){
                    f[i][j][k][l]=max(max(f[i-1][j][k-1][l],f[i-1][j][k][l-1]),max(f[i][j-1][k-1][l],f[i][j-1][k][l-1]))+mapa[i][j]+mapa[k][l];
                    if(i==k&&j==l) f[i][j][k][l]-=mapa[i][j];
                }
    cout<<f[n][n][n][n]<<endl;
    return 0;
}    

第三遍:优化一(n^3)

其实我们可以把这个状态优化成n^3的复杂度。我们发现我们其实i+j和k+l的值是相同的(因为每次两个人都是只走一步),所以我们完全可以用i+j-k来表示l,这样的话就避免了一层循环

#include<cstdio>
#include<algorithm>
using namespace std;
struct point
{
    int x,y,data;
}p[100];
int n,m,map[11][11],f[11][11][11][11];
int main()
{
    int i,j,k,l;
    scanf("%d",&n);
    while(1)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        if(!a&&!b&&!c)
            break;
        p[++m].x=a;
        p[m].y=b;
        p[m].data=c;
    }
    for(i=1;i<=m;i++)
        map[p[i].x][p[i].y]=p[i].data;
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
            for(k=1;k<=n;k++)
            {
                l=i+j-k;
                if(l<=0)
                    break;
                f[i][j][k][l]=max(f[i-1][j][k-1][l],max(f[i-1][j][k][l-1],max(f[i][j-1][k-1][l],f[i][j-1][k][l-1])));
                if(i==k&&j==l)
                    f[i][j][k][l]+=map[i][j];
                else
                    f[i][j][k][l]+=map[i][j]+map[k][l];
            }
    printf("%d
",f[n][n][n][n]);
    return 0;
}

第三遍:优化二(状态压缩),其实和上一个差不多,我们进行状态压缩,f[l][i][ii]表示当前总共走了l步,第一个人在(i,l-i),第二个人在(ii,l-ii),和上面一样进行循环枚举状态就可以

-----------------end-------------------

原文地址:https://www.cnblogs.com/yxr001002/p/14006741.html