NC 16759 枚举子集 + 二维 DP / 四维 DP

159 篇文章 1 订阅
题意

传送门 NC 16759

题解
枚举子集 + 二维 DP

如果只求一条路径,使路径和最大,则为简单的二维 D P DP DP d p [ i ] [ j ] dp[i][j] dp[i][j] 代表从起点走到 ( i , j ) (i,j) (i,j) 的最大路径和
d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) + m a t [ i ] [ j ] dp[i][j]=max(dp[i-1][j], dp[i][j-1])+mat[i][j] dp[i][j]=max(dp[i1][j],dp[i][j1])+mat[i][j] 现在需要求两条路径使路径和最大,那么可以根据组合的思想,枚举第一条路径;即枚举大小为 ( n − 2 ) / 2 (n-2)/2 (n2)/2 的子集, 0 , 1 0,1 0,1 分别代表向下走或向右走。此时问题转化为在改变的方格基础上,求第二条路径使之路径和最大的问题,二维 D P DP DP 求解即可。

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define maxn 15
int dx[2] = {0, 1}, dy[2] = {1, 0};
int n, mat[maxn][maxn], tmp[maxn][maxn], dp[maxn][maxn];

inline int nxt(int i){
    int x = i & -i, y = i + x;
    return ((i & ~y) / x >> 1) | y;
}

int main()
{
    scanf("%d", &n);
    int x, y, z;
    while (~scanf("%d%d%d", &x, &y, &z) && (x | y | z))
    {
        mat[x][y] = z;
    }
    int res = 0, n2 = (n << 1) - 2, m = n2 >> 1;
    for (int i = (1 << m) - 1; i < (1 << n2); i = nxt(i))
    {
        memcpy(tmp, mat, sizeof(mat));
        int x = 1, y = 1, sum = tmp[x][y];
        tmp[x][y] = 0;
        for (int j = 0; j < n2; j++)
        {
            int f = (i >> j) & 1;
            x += dx[f], y += dy[f];
            sum += tmp[x][y], tmp[x][y] = 0;
        }
        for (int j = 1; j <= n; j++)
        {
            for (int k = 1; k <= n; k++)
            {
                dp[j][k] = max(dp[j - 1][k], dp[j][k - 1]) + tmp[j][k];
            }
        }
        res = max(res, sum + dp[n][n]);
    }
    printf("%d\n", res);
    return 0;
}
四维 DP

考虑对两条路径的和进行 D P DP DP d p [ x 1 ] [ y 1 ] [ x 2 ] [ y 2 ] dp[x1][y1][x2][y2] dp[x1][y1][x2][y2] 代表两条路径从起点出发,分别到达 ( x 1 , y 1 ) (x1,y1) (x1,y1) 以及 ( x 2 , y 2 ) (x2,y2) (x2,y2) 的最大路径和。此时状态转移为两条路径的至多两个前驱节点的前进;观察到当两条路径长度相等时,若有相同交点,只可能出现在同一次状态转移,这样就证明了无后效性,即只考虑 x 1 + y 1 = = x 2 + y 2 x1+y1==x2+y2 x1+y1==x2+y2 的状态。实现上使用记忆化搜索。

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
#define maxn 15
int d[4][4] = {{-1, 0, 0, -1}, {-1, 0, -1, 0}, {0, -1, 0, -1}, {0, -1, -1, 0}};
int n, mat[maxn][maxn], dp[maxn][maxn][maxn][maxn];

int rec(int x1, int y1, int x2, int y2)
{
    if (dp[x1][y1][x2][y2] != -1)
        return dp[x1][y1][x2][y2];
    if (x1 == 0 || y1 == 0 || x2 == 0 || y2 == 0)
        return dp[x1][y1][x2][y2] = 0;
    int res = -1, tmp = (x1 == x2 && y1 == y2) ? mat[x1][y1] : mat[x1][y1] + mat[x2][y2];
    for (int i = 0; i < 4; i++)
    {
        res = max(res, tmp + rec(x1 + d[i][0], y1 + d[i][1], x2 + d[i][2], y2 + d[i][3]));
    }
    return dp[x1][y1][x2][y2] = res;
}

int main()
{
    scanf("%d", &n);
    int x, y, z;
    while (~scanf("%d%d%d", &x, &y, &z) && (x | y | z))
    {
        mat[x][y] = z;
    }
    memset(dp, -1, sizeof(dp));
    dp[1][1][1][1] = mat[1][1];
    printf("%d\n", rec(n, n, n, n));
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值