坐标规则型动态规划总结

6 篇文章 0 订阅

前言

最近按照资料里的ppt的内容进行dp的专题学习,今天总结的是坐标规则型的dp总结,题目并不是很难,但是都很有代表性。

例题

Robots

题目描述
在一个 nm 的棋盘内,一些格子里有垃圾要拾捡。现在有一个能捡垃圾的机器人从左上格子里出发,每次只能向右或者向下走。每次他到达一个点,就会自动把这个点内的垃圾拾掉。
问:最多能拾多少垃圾。在最多的情况下,有多少种拾垃圾方案?
数据范围: n100,m100
样例分析
这里写图片描述
最多能拾5块。有4种方法。

解析
因为机器人只能向右或者向下走。符合无后效性原则。于是dp。
F(i,j) 表示从 (1,1) 点开始走到 (i,j) 的时候,最多捡了多少垃圾。
F(i,j)=Maxf(i1,j),f(i,j1)+c[i,j]
其中 C[i,j]=1 表示(i,j)点有垃圾。 C[i,j]=0 表示没有
范围: 1in , 1jm ,决策2种
时间复杂度为 O(nm)

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
typedef long long LL;

const int size = 100+10;
int f[size][size];
bool mp[size][size];
int n,m;

int main() {
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=m;j++) {
            scanf("%d",&mp[i][j]);
        }
    }

    for(int i=1;i<=n;i++) 
        for(int j=1;j<=m;j++) 
            f[i][j]=Max(f[i-1][j],f[i][j-1])+mp[i][j];
    printf("%d\n",f[n][m]);
    return 0;
}

矩阵取数游戏 (NOIP2007)

具体描述见链接:矩阵取数游戏

解析
因为为每一行所取的数与其他行无关,所以直接一行行区间dp就好了:

f[i][j] 为某一行中剩下i到j这些数时的最大值。

f[i][j]=max(f[i+1][i+j]+2mja[i],f[i][i+j1]+2mja[i+j])

然后计算时要高精度…

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

const int maxn=80+10;

struct BIGNUM 
{
    int num[maxn],len;
    BIGNUM(){memset(num,0,sizeof(num));len=1;}
    BIGNUM operator = (const char str[]) 
    {
        len=strlen(str);
        for(int i=0;i<len;i++)
            num[i]=str[len-i-1]-'0';
        while(num[len-1]==0&&len>1) len--;
        return *this;
    }
    BIGNUM operator = (const int n) 
    {
        int tmp=n;
        len=1;
        do 
        {
            num[len-1]=tmp%10;
            tmp/=10;len++;
        }while(tmp>0);
        while(num[len-1]==0&&len>1) len--;
        return *this;
    }
    BIGNUM operator + (const BIGNUM &rhs) const
     {
        BIGNUM tmp;
        tmp.len=max(len,rhs.len)+1;
        for(int i=0;i<tmp.len;i++)
         {
            tmp.num[i]+=num[i]+rhs.num[i];
            tmp.num[i+1]=tmp.num[i]/10;
            tmp.num[i]%=10;
        }
        while(tmp.num[tmp.len-1]==0&&tmp.len>1) tmp.len--;
        return tmp;
    }
    BIGNUM operator * (const BIGNUM &rhs) const 
    {
        BIGNUM tmp;
        tmp.len=len+rhs.len;
        for(int i=0;i<len;i++)
            for(int j=0;j<rhs.len;j++) 
            {
                tmp.num[i+j]+=num[i]*rhs.num[j];
                tmp.num[i+j+1]+=tmp.num[i+j]/10;
                tmp.num[i+j]%=10;
            }
        while(tmp.num[tmp.len-1]==0&&tmp.len>1) tmp.len--;
        return tmp;
    }
    bool operator < (const BIGNUM &rhs) const 
    {
        if(len>rhs.len) return false;
        if(len<rhs.len) return true;
        for(int i=len-1;i>=0;i--)
            if(num[i]!=rhs.num[i]) return num[i]<rhs.num[i];
        return false;
    }
    void print() 
    {
        for(int i=len-1;i>=0;i--) printf("%d",num[i]);
    }
}dp[maxn][maxn];
BIGNUM ans;

int a[maxn];

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    BIGNUM pow;
    pow=2;
    for(int i=1;i<=n;i++) 
    {
        memset(dp,0,sizeof(dp));
        for(int j=1;j<=m;j++) 
        {
            scanf("%d",&a[j]);
            dp[j][j]=a[j]*2;
        }
        for(int x=2;x<=m;x++)
            for(int l=1;l<=m;l++) 
            {
                int r=l+x-1;
                if(r>m) continue;
                dp[l][r]=max(dp[l+1][r]*pow+dp[l][l],dp[l][r-1]*pow+dp[r][r]);//dp
            }
        ans=ans+dp[1][m];
    }
    ans.print();
    return 0;
}

传纸条(NOIP2008)

具体描述见链接:传纸条

解析

刚开始可能想用一个贪心思想:

  • 求出1个纸条从(1,1)到(M,N)的路线最大值.
  • 删除路径上的点值
  • 再求出1个纸条从(M,N) 到(1,1)的路线最大值.
  • 统计两次和
    然后就很容易发现有反例,哈哈。
    然后你想怎么搞呢,既然方向有限定,那么符合dp特性,就可以dp了。
    由于小渊和小轩的路径可逆,因此,尽管出发点不同,但都可以看成同时从 (1,1) 出发到达 (M,N) 点。
    f(i1,j1,i2,j2) 表示纸条1到达 (i1,j1) 位置,纸条2到达 (i2,j2) 位置的最优值。则有,
    这里写图片描述
    (原谅我用mathtype写的,懒得搞成mathjax了)
    其中 (i1,j1)(i2,j2)
    1i1,i2M , 1j1,j2N
    时间复杂度 O(N2M2)

代码

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

const int size=55;
int a[size][size];
int f[size][size][size][size];
int n,m;

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 i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            for(int k=1;k<=n;k++)
            {
                for(int l=1;l<=m;l++)
                {
                    f[i][j][k][l]=max(f[i-1][j][k-1][l],f[i-1][j][k][l-1]);
                    f[i][j][k][l]=max(f[i][j][k][l],f[i][j-1][k-1][l]);
                    f[i][j][k][l]=max(f[i][j][k][l],f[i][j-1][k][l-1]); 
                    f[i][j][k][l]+=(a[i][j]+a[k][l]); 
                    if(i==k&&j==l)
                    {
                        f[i][j][k][l]-=a[i][j];
                    }
                }
            }
        }
    }
    cout<<f[n][m][n][m]<<endl;
    return 0;
}

免费馅饼

题目描述
SERKOI最新推出了一种叫做“免费馅饼”的游戏。
游戏在一个舞台上进行。舞台的宽度为W格,天幕的高度为H格,游戏者占一格。开始时游戏者站在舞台的正中央,手里拿着一个托盘。下图为天幕的高度为4格时某一个时刻游戏者接馅饼的情景。
这里写图片描述
游戏开始后,从舞台天幕顶端的格子中不断出现馅饼并垂直下落。游戏者左右移动去接馅饼。游戏者每秒可以向左或向右移动一格或两格,也可以站在原地不动。
馅饼有很多种,游戏者事先根据自己的口味,对各种馅饼依次打了分。同时,在8-308电脑的遥控下,各种馅饼下落的速度也是不一样的,下落速度以格/秒为单位。
当馅饼在某一秒末恰好到达游戏者所在的格子中,游戏者就收集到了这块馅饼。
输入
输入文件的第一行是用空格隔开的两个正整数,分别给出了舞台的宽度W(1到99之间的奇数)和高度H(1到100之间的整数)。
接下来依馅饼的初始下落时间顺序给出了所有馅饼的信息。每一行给出了一块馅饼的信息。由四个正整数组成,分别表示了馅饼的初始下落时刻(0到1000秒),水平位置、下落速度(1到100)以及分值。游戏开始时刻为0。从1开始自左向右依次对水平方向的每格编号。
输入文件中同一行相邻两项之间用一个或多个空格隔开。
输出
输出文件的第一行给出了一个正整数,表示你的程序所收集的最大分数之和。

解析
刚开始看到这道题却没什么思路,两个物品都同时在动,似乎很复杂的样子。
看了ppt后面的题解后,可以算出每个时刻落到最底层的每个格子有多少分值的馅饼。
如果将馅饼当成参照物,则馅饼向下落,可以看成馅饼不动,人往上走去摘取馅饼,这样人每1时刻都可以走到上一行的5个格子,如图:

这里写图片描述

计算出每个格子每个时刻可能达到的馅饼分值,填入W*H的天幕表。
其中 C[i,j] 表示天幕的第i行第j列的馅饼分值,即第i时刻,馅饼落到最底行的馅饼分值。
F(i,j) 表示人走到第i行j列所取得的馅饼最大分值和,则有,
f(i,j)=maxf(i1,j2)f(i1,j1)f(i1,j)f(i1,j+1)f(i1,j+2)+c(i,j)
范围 0iT,1jW ,决策数为5个
时间复杂度为 O(5WT)

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))

const int size  = 1000+10;
int st;
int f[size][110];
int c[size*10],x[size*10],t[size*10],v[size*10];
int n=1,h,w;
int m=0,time=0;

int mx(int i,int j) {
    int m=0;
    if(f[i+1][j-2]>m and j-2>0) {m=f[i+1][j-2];st=-2;}
    if(f[i+1][j-1]>m and j-1>0) {m=f[i+1][j-1];st=-1;}
    if(f[i+1][j]>m) {m=f[i+1][j];st=0;}
    if(f[i+1][j+1]>m) {m=f[i+1][j+1];st=1;}
    if(f[i+1][j+2]>m) {m=f[i+1][j+2];st=2;}
    return m;
}

int main() {
    freopen("freecake.in","r",stdin);
    scanf("%d%d",&w,&h);
    h--;
    while(~scanf("%d%d%d%d",&t[n],&x[n],&v[n],&c[n])) {
        if(h%v[n]==0) {
            t[n]+=h/v[n];time=Max(time,t[n]);
            n++;
        }
    }
    memset(f,0,sizeof f);
    for(int i=1;i<=n;i++) f[t[i]][x[i]]+=c[i];
    for(int i=time-1;i>=0;i--)
        for(int j=w;j>0;j--)
            f[i][j]+=mx(i,j);
    printf("%d\n",f[0][w/2+1]);
    for(int i=0,j=w/2+1;;i++) {
        if(mx(i,j)==0) break;
        j+=st;printf("%d\n",st);
    }
    return 0;
}

三角蛋糕

题目描述
一块边长为n的正三角形的大蛋糕,一些被老鼠咬坏了,如下图黑色部分。
这里写图片描述
现想把该蛋糕切出一块最大的没被老鼠咬坏正三角形的蛋糕;
求切出的最大的三角形面积
范围: n100

解析
设顶点坐标为 (i,j) 的三角形最大高度为 F(i,j)
显然: f(i,j)=min(f(i1,j1),f(i1,j+1))+1
C[i1,j] =’-‘,表示这个三角形没被老鼠咬坏。
同理
设顶点坐标为 (i,j) 的三角形最大高度为 G(i,j)
显然: g(i,j)=min(g(i+1,j1),g(i+1,j+1))+1
C[i+1,j] =’-‘,表示这个三角形没被老鼠咬坏。
然后分别求 F(i,j) G(i,j)
1in , 1j2n1
因此,时间复杂度 O(N2)
答案为高度的平方,简单证明如下:
假设最大高度为W
则三角形个数为 ++++2W1=W2

代码

#include <cstdio>
#include <cstring>
#include <algorithm>

#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
typedef long long LL;

const int size = 200+10;

int a[size][size],n;
char st[size];

int main() {
    scanf("%d",&n);
    for(int i=1;i<=n;i++) {
        scanf("%s",st+i);
        for(int j=1;j<=2*n-i;j++) {
            if(st[j]=='-') a[i][j]=a[i][j-1]+1;
            else a[i][j]=0;
        }
    }

    int ans=0,k;
    for(int i=1;i<=n;i++)
        for(int j=i;j<=2*n-i;j++) {
            if(a[i][j]) {
                if(!(i*j&1) and (i&1||j&1))
                    for(k=1;a[i+k][j+k]>=k*2+1;k++);
                else
                    for(k=1;a[i-k][j+k]>=k*2+1;k++);
                ans=Max(ans,k*k);
            }
        }

    printf("%d\n",ans);
    return 0;
}

总结

坐标规则类动态规划有一个共性,那就是在一个矩阵中(一般是二维矩阵,当然可能有更加复杂的图形)给出一些规则,然后按规则去做某些决策。
思考这类问题的基本方法是:以坐标为状态,坐标之间的转换关系,一般利用问题给出的规则进行决策转移。
状态转移方程一般可描述如下:
F(i,j)=Max f(i1,k)+ ;这里k为规则数

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值