Loading

坐标dp

坐标dp看起来和区间dp差别不大,其实差别还真不大还是有点差别的,区间dp \(\Large f[l,r]\)通常表示对于 \(\Large [l,r]\)这个区间最大值/最小是/总计数之类的,但是坐标dp \(\Large f(x,y)\)往往表示的是对于 \(\Large (x,y)\)这个点的一些操作(不一定非要是 \(\Large (x,y)\)的形式,具体见后面例题)

直接上题

思路:其实感觉前面那个矩阵取数不就是加了for循环的区间dp吗?扩展一维你以为就能进化成坐标dp吗,这个题坐标dp的特性很明显,并且有些难度。

这道题目要求我们走两次,我们先考虑走一次的情况,我们假设\(\Large f[i][j]\)是他走到\(\Large (i,j)\)为止得到的最大值,由于他只能向下或者向右走,故前一个状态只能由两个状态转移而来,我们不难写出方程

\[\Large f[i][j]=max(f[i-1][j],f[i][j-1])+s[i][j] \]

其中s[i][j]就是走到那的好心程度,由输入得到

很显然,对于走两次,我们并不能分开走两次,而是应该把走第二次看成一个约束,还记的之前的二维背包问题吗?其实这运用了同样的思想(包括二维最短路也是同样的思想),当限制条件多了一,我们并没有方法通过次数的叠加来实现我们的目的的时候,我们就可以考虑给我们的状态加一个维度,来同时实现这两种约束。

对于本题,我们可以直接暴力加上一个坐标的维度,即两个维度写出方程

\[\Large f(i_1,j_1,i_2,j_2)=max\left\{\begin{aligned} f(i_1-1,j_1,i_2-1,j_2)+s[i_1,j_1]+s[i_2,j_2] \\ f(i_1,j_1-1,i_2-1,j_2)+s[i_1,j_1]+s[i_2,j_2] \\ f(i_1-1,j_1,i_2,j_2-1)+s[i_1,j_1]+s[i_2,j_2] \\ f(i_1,j_1-1,i_2,j_2-1)+s[i_1,j_1]+s[i_2,j_2] \end{aligned}\right. \]

这这样就可以得到答案,时间复杂度为\(\Large \Theta(N^4)\),对于50来说是可以接受的,但是由于本题方向的唯二性(只能向左或者向下)和不停性(不能停下来不走),我们不难发现\(\Large i_1+j_1\ === i_2+j_2\)(三个等于代表恒等于),所以,我们可以令\(\Large k = i_1+j_1\),那么\(\Large k = i_2+j_2\),不难发现,其中k恰好就是走过的步数,那么我们可以用\(\Large f(k,i_1,i_2)\)来表示一个状态,其中\(\Large j_1=k-i_1,j_2=k-i_2\),这样,我们写出优化后的方程

\[\Large f(k,i_1,i_2)=max\left\{\begin{aligned} f(k,i_1,i_2-1)+s[i_1,j_1]+s[i_2,j_2] \\ f(k,i_1-1,i_2)+s[i_1,j_1]+s[i_2,j_2] \\ f(k,i_1,i_2)+s[i_1,j_1]+s[i_2,j_2] \\ f(k,i_1-1,i_2-1)+s[i_1,j_1]+s[i_2,j_2] \end{aligned}\right. \]

时间复杂度是\(\Large \Theta(N^3)\)

这里还需要考虑几个小问题

如果\(\Large x_1==x_2\),我们只能加一次

考虑枚举的起始问题,由题,我们知道\(\Large 1\leqslant i\leqslant n,1\leqslant j\leqslant m\)由因为\(\Large j == k-i\),我们将其替换,得到i的范围\(\Large k-m\leqslant i\leqslant k-1\),综上所述\(\Large max(k-m,1)\leqslant i\leqslant min(n,k-1)\)

code

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

const int N = 55 ;

inline int read(){
    int w = 0 ; char ch = getchar() ;
    while( ch < '0' || ch > '9' ) ch = getchar() ;
    while( ch >= '0' && ch <= '9' ) {
        w = ( w << 1 ) +  ( w << 3 ) + ( ch - '0' ) ;
        ch = getchar() ;   
    } return w ;
}

int n , m ;
int w [N][N] ;
int f [2 * N][N][N] ;

void sc(){
    n = read() , m = read() ;
    for( int i = 1 ; i <= n ; i ++ )
        for( int j = 1 ; j <= m ; j ++ )
            w [i][j] = read() ;
}

void work(){
    for( int k = 2 ; k <= n + m ; k ++ ){
        for( int x1 = max( k - m , 1 ) ; x1 <= min( n , k - 1 ) ; x1 ++ ){
            for( int x2 = max( k - m , 1 ) ; x2 <= min( n , k - 1 ) ; x2 ++ ){
                int t = w [x1][k - x1] ;
                if( x1 != x2 || k - x1 != k - x2 ) t += w [x2][k - x2] ;
                f [k][x1][x2] = max( f [k][x1][x2] , f [k - 1][x1 - 1][x2] + t ) ;
                f [k][x1][x2] = max( f [k][x1][x2] , f [k - 1][x1 - 1][x2 - 1] + t ) ;
                f [k][x1][x2] = max( f [k][x1][x2] , f [k - 1][x1][x2- 1] + t ) ;
                f [k][x1][x2] = max( f [k][x1][x2] , f [k - 1][x1][x2] + t ) ;
            }
        }
    }
    printf( "%d" , f [m + n][n][n] ) ;
}

signed main(){
    sc() ;
    work() ;
    return 0 ;
}

2

思路:这道题其实是一道加强版的数字金字塔,与金字塔不同的是它可以左右走,还是一个环形的结构,这个环形结构与区间dp中我们常见的破环为链的处理方法不同,如果题目中的山每一层宽度都一样,我们可以考虑破环为链,对于这道题来说,山的宽度从低到高逐渐减小,所以我们无法整体破环为链。但是,对于每一层来说,我们可以破环为链,然后进行刷新 。

总思路:先进行上下刷新,上下刷完之后,对于每一层,破环为链,左刷右刷就可以了

code

view code

#include<bits/stdc++.h>
#define R register int
#define int long long
using namespace std ;

const int N = 2e3 + 10 ;

inline int read(){
    int w = 0 ; char ch = getchar() ;
    while( ch < '0' || ch > '9' ) ch = getchar() ;
    while( ch >= '0' && ch <= '9' ){
        w = ( w << 1 ) + ( w << 3 ) + ( ch - '0' ) ;
        ch = getchar() ;   
    } return w ; 
}

int n ;
int w [N][2 * N] ;
int f [N][N] ;
int s [2 * N] ;

void sc(){
    n = read() ;
    memset( w , 127 / 3 , sizeof( w ) ) ;
    for( int i = 1 ; i <= n ; i ++ )
    for( int j = 1 ; j <= i ; j ++ )
        w [i][j] = read() , w [i][i + j] = w [i][j] ;
    f [1][1] = w [1][1] ;
}


void work(){
    for( int i = 2 ; i <= n ; i ++ ){
        for( int j = 1 ; j < i ; j ++ )
        f [i][j] = min( f [i - 1][j] , f [i - 1][j - 1] ) + w [i][j] ;
        int t = min( f [i - 1][1] , f [i - 1][i - 1] ) ;
        f [i][1] = t + w [i][1] ;
        f [i][i] = t + w [i][i] ;
        //up down 
 /*       for( int j = i - 1 ; j >= 1 ; j -- )
        f [i][j] = min( f [i][j] , f [i][j + 1] + w [i][j] ) ;
        f [i][i] = min( f [i][i] , f [i][1] + w [i][i] ) ;
        for( int j = 2 ; j <= i ; j ++ )
        f [i][j] = min( f [i][j] , f [i][j - 1] + w [i][j] ) ;
        f [i][1] = min( f [i][1] , f [i][i] + w [i][1] ) ;*/
        memset( s , 0 , sizeof( s ) ) ;
        for( R j = 1 ; j <= i ; j ++ )
          s [j] = f [i][j] , s [j + i] = f [i][j] ;
 //       for( R j = 1 ; j <= 2 * i ; j ++ )
  //      printf( "%lld " , s [j] ) ;
  //      s [1] = min( s [1] , s [n] + w [i][1] ) ;
        for( R j = 2 ; j < 2 * i ; j ++ )
          s [j] = min( s [j] , s [j - 1] + w [i][j] ) ;
        for( R j = 2 * i - 1 ; j > 1 ; j -- )
          s [j] = min( s [j] , s [j + 1] + w [i][j] ) ;
        for( R j = 1 ; j <= i ; j ++ )
          f [i][j] = min( s [j] , s [j + i] ) ;
  //      printf( "\n" ) ;

    }
    printf( "%lld" , f [n][1] ) ;
}

signed main(){
    sc() ;
    work() ;
    return 0 ;
}

3

思路:其实这是一个各个区间中稍有羁绊的坐标dp,说他是区间dp也不是不可以,提取出来数学模型 。推区间dp的方程即可

摘自网上

给定一个f*v的矩阵

要求从第一行走到第f行,每行取走一个数,

且该行所取的数必须必上一行所取的数的列数大 , 求所能取走的最大值

注意每一行所取走的数字的列数必须大于等该行的行号

因为必须给前面的花留下足够的花瓶

同理每一行所能取的最大的花瓶号必须小于等于v-(f-该行行数)

由此我们便可以很容易的得出状态转移方程

\[\Large dp[i][j]=max(dp[i-1][k])+d[i][j](k<j) \]

其中\(dp[i][j]\)表示从第一行走到第i行并取走该行第j个数所能取得的最大值.

方案输出具体操作见路径记录专题

code

view code

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

const int N = 110 ;
inline int read(){
    int w = 0 , f = 1 ; char ch = getchar() ;
    while( ch < '0' || ch > '9' ){
        if( ch == '-' ) f = -1 ;
        ch = getchar() ;
    }
    while( ch >= '0' && ch <= '9' ){
        w = ( w << 1 ) + ( w << 3 ) + ( ch - '0' ) ;
        ch = getchar() ; 
    }
    return f * w ;
}

int F , v ;
int w [N][N] ;
int f [N][N] ; // 前i朵花放在前j个瓶子内最大利益
int path [N][N] ;

void sc(){
    F = read() , v = read() ;
    for( int i = 1 ; i <= F ; i ++ )
    for( int j = 1 ; j <= v ; j ++ )
    w [i][j] = read() ;
    memset( f , - 127 / 3 , sizeof( f ) ) ; // 由于可能为观赏度可能为负的
}

void work(){
    for( int i = 1 ; i <= F ; i ++ ) f [0][i] = 0 ;
    for( int i = 1 ; i <= v ; i ++ ) f [1][i] = w [1][i] ;
    for( int i = 2 ; i <= F ; i ++ ){
        for( int j = i ; j <= v - ( F - i ) ; j ++ ){//留给后面的瓶子
            for( int k = i - 1 ; k < j ; k ++ ){//枚举决策点
                if( f [i - 1][k] + w [i][j] > f [i][j] ){
                   f [i][j] = f [i - 1][k] + w [i][j] ; 
                   path [i][j] = k ;
 //                  printf( "%lld %lld %lld\n" , i , j , path [i] ) ;
                } 
            }
        }
    }
    int anss = 0 , ansi = 0 ;
    for( int i = 1 ; i <= v ; i ++ )
    if( f [F][i] > anss ) anss = f [F][i] , ansi = i ;  
    printf( "%d\n" , anss ) ;
    int ans[N] ; ans [F] = ansi ;
    for( int i = F ; i >= 2 ; i -- )
        ans [i - 1] = path[i][ans [i]] ;
    for( int i = 1 ; i <= F ; i ++ )
    printf( "%d " , ans [i] ) ; 
    
}

signed main(){
    sc() ;
    work() ;
    return 0 ;
}

4

思路:根据题目分析,我们设\(\Large f[i,j]\)为i时刻在j位置所得到的最大分数,那么\(\Large max(f[las,k])\|k\in[1,w]\)就是答案,其中w为场地的宽度,las为最后的时间

不难发现,一个状态可以由五个状态转移而来,分别是:不动,左1,左2,右1,右2;

写出方程:

\[\Large f [i,j] = \left\{\begin{aligned} f [i-1,j-1] + w[i,j] \\ f [i-1,j+1] + w[i,j] \\ f [i-1,j-2] + w[i,j] \\ f [i-1,j+2] + w[i,j] \\ f [i-1,j] + w[i,j] \\ \end{aligned}\right. \]

\(w[i,j]\)为i时刻在j位置的得分
路径记录见路径记录专题

code

view code

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

inline int read(){
    int w = 0 ; char ch = getchar() ;
    while( ch > '9' || ch < '0' ) ch = getchar() ;
    while( ch >= '0' && ch <= '9' ){
        w = ( w << 1 ) + ( w << 3 ) + ( ch - '0' ) ;
        ch = getchar() ;
    } return w ;
}
 
const int N = 1e2 + 10 ;
const int M = 1e3 + 10 ;
int s , h ;
int w [M][N] ;
int f [M][N] ;
int path [M][N] ;
int las , ct ;

void sc(){
    s = read() , h = read() , h -- ;
    ct = s / 2 ; ct ++ ; 
    
        int t , x , v , ws , rt ;
    while( ~scanf( "%lld" , &t ) ){
        x = read() , v = read() , ws = read() ;
        if( h % v == 0 )
        rt = t + ( h / v ) ;
        las = max( las , rt ) ;
        w [rt][x] += ws ;
    } //printf( "%lld %lld" , ct , las ) ;
/*    for( int i = 1 ; i <= s ; i ++ )
    for( int j = 1 ; j <= 10 ; j ++)
    printf( "%lld %lld %lld\n" , i , j , w [i][j] ) ; */
}

void work(){
    memset( f , -1 , sizeof( f ) ) ;
    f [0][ct] = 0 ;
    for( int i = 1 ; i <= las ; i ++ ){
        for( int j = 1 ; j <= s ; j ++ ){
            int t = w [i][j] ;
            for( int k = -2 ; k <= 2 ; k ++ ){
                if( j + k < 1 || j + k > s ) continue ;
                if( f [i][j] < f [i - 1][j + k] + t ){
                    f [i][j] = f [i - 1][j + k] + t ;
                    path [i][j] = -k ;
                }
            }
        }
    }
    int ans = 0 , maxx = 0 , maxy = 0 ;
    for( int i = 1 ; i <= las ; i ++ ){
        for( int j = 1 ; j <= s ; j ++ ){
            if( f [i][j] > ans ){
                ans = f [i][j] ;
                maxx = i , maxy = j ;
            }
        }
    }
    printf( "%lld\n" , ans ) ;
    stack<int> a;
    while( maxx ){
  //      printf( "%lld %lld %lld\n" , maxx , maxy , path [maxx][maxy] ) ;
        a.push( path [maxx][maxy] ) ;
        maxy -= path [maxx][maxy] ;
        maxx -- ;
    }
    while( !a.empty() ){
        printf( "%lld\n" , a.top() ) ;
        a.pop() ;
    }
}

signed main(){
    sc() ;
    work() ;
    return 0 ;
}

posted @ 2021-02-25 23:04  Soresen  阅读(413)  评论(0)    收藏  举报