『进阶DP专题:二维DP初步』



<更新提示>

<第一次更新>



<正文>

二维动态规划

初步

二维动态规划并不是指动态规划的状态是二维的,而是指线性动态规划的拓展,由线性变为了平面,即在一个平面上做动态规划。

例题

马拦过河卒

题目描述

 棋盘上A点有一个过河卒,需要走到目标B点。卒行走的规则:可以向下、或者向右。同时在棋盘上C点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。   棋盘用坐标表示,A点(0, 0)、B点(n, m)(n, m为不超过15的整数),同样马的位置坐标是需要给出的。现在要求你计算出卒从A点能够到达B点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。

输入格式

一行四个数据,分别表示B点坐标和马的坐标。

输出格式

一个数据,表示所有的路径条数。

样例数据

input

6 6 3 3

output

6

数据规模与约定

时间限制:1s

空间限制:256MB

分析

这是一道二维动态规划的入门题,其考点为加法原理。我们直接设置状态f[i][j]代表走到棋盘上坐标为(i,j)的点的路径条数。那么由于卒只能向下或向右走,所以坐标为(i,j)的点只能由(i-1,j),(i,j-1)走来,那么由加法原理可知,走到该点的路径数就是走到以上两点的路径数相加。所以,根据题意,我们得出状态转移方程:\[f[i][j]=\begin{cases}sum(f[i-1][j],f[i][j-1])((i,j)未被马控制)\\0((i,j)被马控制)\end{cases}\]

我们只需处理出哪些点被马控制即可,这个问题只需在一个新的二维数组根据题意进行特殊标记即可。至于初始值,第一行和第一列的所有位置都只有一种走法,即f[i][0]=f[0][i]=1,当然,第一行和第一列里被马控制的点也是不能走的,那么最终的答案就是f[n][m]。

代码实现如下:

#include<bits/stdc++.h>
using namespace std;
int Map[1080][1080]={},Ma[1080][1080]={},x,y,n,m;
int dx[8]={2,2,1,1,-1,-1,-2,-2};
int dy[8]={-1,1,-2,2,-2,2,-1,1};
int main()
{
    cin>>n>>m>>x>>y;
    Ma[x][y]=-1;
    for(int i=0;i<8;i++)
    {
        if(x+dx[i]>=0&&y+dy[i]>=0)Ma[x+dx[i]][y+dy[i]]=-1;
    }
    Map[0][0]=1;
    for(int i=0;i<=n;i++)if(Ma[i][0]==0)Map[i][0]=1;
    else
    {
        for(int j=i;j<=n;j++)Ma[j][0]=-1;
        break;
    }
    for(int i=0;i<=m;i++)if(Ma[0][i]==0)Map[0][i]=1;
    else
    {
        for(int j=i;j<=m;j++)Ma[0][j]=-1;
        break;
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(Ma[i][j]!=-1)
            {
                Map[i][j]=Map[i-1][j]+Map[i][j-1];
            }
        }
    }
    cout<<Map[n][m]<<endl;
    return 0;
} 
农田个数

题目描述

你的老家在农村。过年时,你回老家去拜年。你家有一片N×M农田,将其看成一个N×M的方格矩阵,有些方格是一片水域。你的农村伯伯听说你是学计算机的,给你出了一道题: 他问你:这片农田总共包含了多少个不存在水域的正方形农田。

两个正方形农田不同必须至少包含下面的两个条件中的一条:

边长不相等

左上角的方格不是同一方格

输入格式

输入数据第一行为两个由空格分开的正整数N、M(1<=m< n <=1000)

第2行到第N+1行每行有M个数字(0或1),描述了这一片农田。0表示这个方格为水域,否则为农田(注意:数字之间没有空格,而且每行不会出现空格)

输出格式

满足条件的正方形农田个数。

样例数据

input

3 3

110

110

000

output

5

样例解释 边长为1的正方形农田有4块 边长为2的正方形农田有1块 合起来就是5块

数据规模与约定

时间限制:1s

空间限制:256MB

分析

这题也是明显的平面二维dp,简单的方法就是直接设置状态f[i][j]代表以格子(i,j)为右下角的正方形个数,那么显然,他也代表了以(i,j)为右下角构成的正方形的最大边长,我们考虑如何求解。

先考虑一种简单情况,如果(i-1,j),(i,j-1),(i-1,j-1)三个点均能作为一个边长为k的正方形的右下角,画图可知,那么点(i,j)一定能作为一个边长为(k+1)的正方形的右下角。

(此时k=2,(i,j)一定能作为一个边长为3的正方形的右下角)

其实,简单推理可知,点(i,j)作为右下角能构成的正方形的最大边长即为之前提到三点中((i-1,j),(i,j-1),(i-1,j-1))能够成正方形的最大边长的最小值加一,那么以点(i,j)作为右下角的正方形个数也是该值。即状态转移方程为:

\[f[i][j]=min(f[i-1][j],f[i][j-1],f[i-1][j-1])+1\]

我们可以两重循环暴力求解f数组,对f数组的每一个值求和即为答案。

代码实现如下:

#include<bits/stdc++.h>
using namespace std;
int n,m,f[1008][1008]={},ans=0;
string Map[1008];
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>Map[i];
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<m;j++)
        {
            if(Map[i][j]==‘1‘)
            {
                f[i][j]=min(min(f[i-1][j],f[i][j-1]),f[i-1][j-1])+1;
                ans+=f[i][j];
            }
        }
    }
    cout<<ans;
}
矩阵切割

题目描述

给你一个矩阵,其边长均为整数。你想把矩阵切割成总数最少的正方形,其边长也为整数。切割工作由一台切割机器完成,它能沿平行于矩形任一边的方向,从一边开始一直切割到另一边。对得到的矩形再分别进行切割。

输入格式

输入文件中包含两个正整数,代表矩形的边长,每边长均在1—100之间。

输出格式

输出文件包含一行,显示出你的程序得到的最理想的正方形数目。

样例数据

input

5 6

output

5

样例解释

数据规模与约定

时间限制:1s

空间限制:256MB

分析

这道题也是在平面上动态规划,即二维dp。不过这道题的样例很良心,显然不能每一次直接切割最大的正方形。设置状态f[i][j]代表1到i,1到j构成的矩形的最小切割数。直接能得出的初始条件就是f[i][i]的最优值一定是1,因为这是一个正方形,可以直接切割。那么不是这种情况时,这个矩形一定被分割为若干个更小的矩形或正方形,更小的矩形也是如此。我们只要在i,j之间不断枚举分割点k就能求得最大值,即进行状态转移。所以状态转移方程如下:

\[f[i][j]=max(f[i][j],f[k][j]+f[i-k][j])(横向切割,k=1...i-1)\\f[i][j]=max(f[i][j],f[i][k]+f[i][j-k])(纵向切割,k=1...j-1)\]

三重循环进行转移,f[n][m]即为答案。

代码实现如下:

#include<bits/stdc++.h>
using namespace std;
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
inline void read(int &k)
{
    int x=0,w=0;char ch;
    while(!isdigit(ch))w|=ch==‘-‘,ch=getchar();
    while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    k=(w?-x:x);return;
}
inline void print(int x)
{
    int y=10,len=1;
    while(y<=x)y*=10,len++;
    while(len--){y/=10,putchar(x/y+48),x%=y;}
}
int n,m,f[180][180]={};
int main()
{
    read(n),read(m);
    memset(f,0x3f,sizeof(f));
    for(register int i=1;i<=min(n,m);i++)f[i][i]=1;
    for(register int i=1;i<=n;i++)
    {
        for(register int j=1;j<=m;j++)
        {
            for(register int k=1;k<i;k++)
            {
                f[i][j]=min(f[i][j],f[k][j]+f[i-k][j]);
            }
            for(register int k=1;k<j;k++)
            {
                f[i][j]=min(f[i][j],f[i][k]+f[i][j-k]);
            }
        }
    }
    print(f[n][m]);
    return 0;
}
创意吃鱼

题目描述

可爱猫猫家里长方形大池子中有很多鱼,她开始思考:到底要以何种方法吃鱼呢(猫猫就是这么可爱,吃鱼也要想好吃法 ^_*)。她发现,把大池子视为01矩阵(0表示对应位置无鱼,1表示对应位置有鱼)有助于决定吃鱼策略。

在代表池子的01矩阵中,有很多的正方形子矩阵,如果某个正方形子矩阵的某条对角线上都有鱼,且此正方形子矩阵的其他地方无鱼,猫猫就可以从这个正方形子矩阵“对角线的一端”下口,只一吸,就能把对角线上的那一队鲜鱼吸入口中。    猫猫是个贪婪的家伙,所以她想一口吃掉尽量多的鱼。请你帮猫猫计算一下,她一口下去,最多可以吃掉多少条鱼?

输入格式

  第一行有两个整数n和m(n,m≥1),描述池塘规模。接下来的n行,每行有m个数字(非“0”即“1”)。每两个数字之间用空格隔开。

输出格式

只有一个整数——猫猫一口下去可以吃掉的鱼的数量,占一行,行末有回车。

样例数据

input

4 6

0 1 0 1 0 0

0 0 1 0 1 0

1 1 0 0 0 1

0 1 1 0 1 0

output

3

数据规模与约定

对于30%的数据,有n,m≤100

对于60%的数据,有n,m≤1000

对于100%的数据,有n,m≤2500

时间限制:1s

空间限制:256MB

分析

这道题求的是最大全1对角线长度,且要求对角线所在正方形其他地方全是0。本质上这道题和农田个数是相同的。先考虑左上角到右下角的对角线:我们设f1[i][j]代表以点(i,j)为右下角该种对角线的最大长度。分析后可以得知约束该值的和农田个数一题相同,有三个值。分别是f1[i-1][j-1],点(i,j)左边连续0的个数,点(i,j)上门连续0的个数,f1[i][j]即为他们三个数的最小值加一。后两个值首先保证了对角线所在正方形中最下面一行和最右边一列除右下角外全是0,而f1[i-1][j-1]则保证了倒数第二行和右边倒数第二列除他本身外也全是0,而这个值需要1f[i-2][j-2]保证,递归下去,就能够保证这个对角线所在的正方形中除了对角线以外其他值全都是0,符合题意要求。也可以根据定义,直接认为f[i-1][j-1]直接保证了对角线长度为该值时,所在正方形中其余元素全为0。我们可以预处理left[i][j]代表第i行第j个数以前有多少连续的0,up[i][j]代表第i列第j个数以上有多少个连续的0,这样我们就能实现状态转移,状态转移方程如下:

\[f1[i][j]=min(f1[i-1][j-1],left[i][j],up[i][j])+1\]

那么同理,f2[i][j]代表以以点(i,j)为左下角从右上角到左下角的对角线的最大长度,预处理出right数组就能实现状态转移:

\[f2[i][j]=min(f2[i-1][j+1],right[i][j],up[i][j])+1\]

在每一个点中寻找f1,f2的最大值,本题完美解决。

代码实现如下:

#include<bits/stdc++.h>
using namespace std;
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
inline void read(int &k)
{
    int x=0,w=0;char ch;
    while(!isdigit(ch))w|=ch==‘-‘,ch=getchar();
    while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    k=(w?-x:x);return;
}
inline void print(int x)
{
    int y=10,len=1;
    while(y<=x)y*=10,len++;
    while(len--){y/=10,putchar(x/y+48),x%=y;}
}
int n,m,Map[3000][3000]={},Right[3000][3000]={},Left[3000][3000]={},Up[3000][3000]={};
int f1[3000][3000]={},f2[3000][3000]={},ans=0;
int main()
{
    freopen("meal.in","r",stdin);
    freopen("meal.out","w",stdout);
    read(n),read(m);
    for(register int i=1;i<=n;++i)
    {
        for(register int j=1;j<=m;++j)
        {
            read(Map[i][j]);
        }
    }
    for(register int i=1;i<=n;++i)
    {
        for(register int j=2;j<=m;++j)
        {
            if(!Map[i][j-1])Left[i][j]=Left[i][j-1]+1;
        }
        for(register int j=m-1;j>=1;--j)
        {
            if(!Map[i][j+1])Right[i][j]=Right[i][j+1]+1;
        }
    }
    for(register int i=1;i<=m;++i)
    {
        for(register int j=2;j<=n;++j)
        {
            if(!Map[j-1][i])Up[j][i]=Up[j-1][i]+1;
        }
    }
    for(register int i=1;i<=n;++i)
    {
        for(register int j=1;j<=m;++j)
        {
            if(Map[i][j])f1[i][j]=min(f1[i-1][j-1],min(Left[i][j],Up[i][j]))+1,ans=max(ans,f1[i][j]);
        }
    }
    for(register int i=1;i<=n;++i)
    {
        for(register int j=m;j>=1;--j)
        {
            if(Map[i][j])f2[i][j]=min(f2[i-1][j+1],min(Right[i][j],Up[i][j]))+1,ans=max(ans,f2[i][j]);
        }
    }
    cout<<ans<<endl;
    return 0;
}

总结

二维dp的标志是将线性扩展到了平面。而解决该类问题,需要我们更恰当的设置状态,仔细思考如何建立状态转移方程,求解问题。其核心思想还是在于将各个阶段联系起来,需要多做题训练。



<后记>



<废话>

原文地址:https://www.cnblogs.com/Parsnip/p/9863049.html

时间: 2024-10-13 16:51:50

『进阶DP专题:二维DP初步』的相关文章

XTU1168:Alice and Bob(二维DP)

摘要:Dota(Defence of the Ancients,远古的守护), 是指基于魔兽争霸3:冰封王座(暴雪娱乐公司出品)的多人即时对战自定义地图,可支持10个人同时连线游戏.Dota以对立的两个小队展开对战,通常是5v5,游戏目的是守护自己的远古遗迹(近卫方的生命之树.天灾方的冰封王座),同时摧毁对方的远古遗迹.DotA是目前唯一被暴雪娱乐公司官方认可的魔兽争霸RPG.Dota在大学生中的风靡程度令人咂舌,而随着玩家对游戏的理解深入,本身存在于游戏中的许多数学模型被挖掘出来进行研究.游戏

HDU 4901 The Romantic Hero(二维dp)

题目大意:给你n个数字,然后分成两份,前边的一份里面的元素进行异或,后面的一份里面的元素进行与.分的时候按照给的先后数序取数,后面的里面的所有的元素的下标一定比前面的大.问你有多上种放元素的方法可以使得前面异或的值和后面与的值相等. dp[x][y] 表示走到第x步,得到y这个数字一共有多少种方法. 但是需要注意这里得分一下,不能直接用dp数组存种数,你需要分一下从上一层过来的次数,和这一层自己可以到达的次数.然后取和的时候前后两个集合的种数进行乘法,注意边乘边取余. 顺便给一组数据: 4 3

二维dp(O(N^3)实现) zoj3230

1 #include<iostream> 2 #include<string.h> 3 #include<stdio.h> 4 #define maxn 125 5 using namespace std; 6 7 int cost[maxn][maxn],w[maxn][maxn]; 8 int dp[maxn][maxn]; 9 int N,M; 10 int main(){ 11 while(cin>>N>>M){ 12 if (N==0

hoj_10014_二维DP

The Triangle Time Limit: 1000ms, Special Time Limit:2000ms, Memory Limit:32768KB Total submit users: 952, Accepted users: 860 Problem 10014 : No special judgement Problem description 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 (Figure 1) Figure 1 shows a number tr

HDU 5074 Hatsune Miku(简单二维dp)

题目大意:给你一些音符之间的联系,给你一个串,让你求出这个串的最大值.-1的时候可以任意替代,其他情况必须为序列上的数. 解题思路:简单二维dp,分情况处理就可以了啊. Hatsune Miku Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Others) Total Submission(s): 637    Accepted Submission(s): 458 Problem De

(review)zoj4800 二维dp 状态转移很灵活

1 #include<iostream> 2 #include<stdio.h> 3 4 using namespace std; 5 6 double dp[10005][125]; 7 double p[125][125]; 8 int pk[10005]; 9 10 int N,M; 11 12 double fmax(double a,double b){ 13 if(a-b>0) return a;else return b; 14 } 15 int main(){

NOJ1060 接苹果 二维DP

题目描述 很少有人知道奶牛爱吃苹果.农夫约翰的农场上有两棵苹果树(编号为1和2), 每一棵树上都长满了苹果.奶牛贝茜无法摘下树上的苹果,所以她只能等待苹果 从树上落下.但是,由于苹果掉到地上会摔烂,贝茜必须在半空中接住苹果(没有人爱吃摔烂的苹果).贝茜吃东西很快,她接到苹果后仅用几秒钟就能吃完.每一分钟,两棵苹果树其中的一棵会掉落一个苹果.贝茜已经过了足够的训练, 只要站在树下就一定能接住这棵树上掉落的苹果.同时,贝茜能够在两棵树之间 快速移动(移动时间远少于1分钟),因此当苹果掉落时,她必定站

洛谷p1732 活蹦乱跳的香穗子 二维DP

今天不BB了,直接帖原题吧  地址>>https://www.luogu.org/problem/show?pid=1732<< 题目描述 香穗子在田野上调蘑菇!她跳啊跳,发现自己很无聊,于是她想了一个有趣的事情,每个格子最多只能经过1次,且每个格子都有其价值 跳的规则是这样的,香穗子可以向上下左右四个方向跳到相邻的格子,并且她只能往价值更高(这里是严格的大于)的格子跳. 香穗子可以从任意的格子出发,在任意的格子结束, 那么她最多能跳几次? 输入输出格式 输入格式: 第一行n,m,

二维dp

原题http://acm.hdu.edu.cn/showproblem.php?pid=3127 WHUgirls Time Limit: 3000/2000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others) Total Submission(s): 2050    Accepted Submission(s): 780 Problem Description There are many pretty girls i