HDU 1693 Eat the Trees (插头dp)

本文介绍了一种使用插头动态规划(DP)解决特定类型地图问题的方法。具体而言,文章详细阐述了如何通过插头DP策略来规划路径,使英雄能够按照规定的规则吃掉地图上所有标记为1的格子中的树。文中还提供了代码实现,帮助读者更好地理解这一算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:http://acm.hdu.edu.cn/showproblem.php?pid=1693

题意:

这是一道插头dp的入门题

给出一个n*m的地图,标记为1的格子上有树,有个英雄要吃掉所有的树,吃树的时候要遵循规则:

1)一圈一圈地吃(只能走上下左右,每个圈至少4个格子,头尾相连);

2)只能走有树的格子,吃过的树就消失了,即圈不能相交,每个格子只能走1次;

3)可以分成几个圈来吃。

思路:

1)概念

【插头】每个格子有四条边,每条边上可以插一个插头(与边垂直)。因为每个格子只能经过一次,即在每个格子中选一条边进一条边出,每个格子必插两个插头。

【轮廓线】

轮廓线形状:

dp[ i ][ j ][ k ] 表示的轮廓线经过第 i 行第 j 列格子的下边和右边

如dp[1][2][k]的轮廓线:


给轮廓线经过的每条边编号,每条边为一个插头位置

用过状态压缩枚举状态k,如

0 1 2 3 4

1 0 1 1 1    表示第0,2,3,4条边上的插头插上了, 如下


2)步骤

从左上角开始,一格一格枚举状态(轮廓线在经该格的右边和下边)

状态k下,x为该格的下边,y为右边

    若xy边都有插头,则该格的左边和上边没有插头

    若xy边都没插头,则该格的左边和上边都有插头

    若x,y边中只有1边有插头,则该格的另一个插头在左边或上边上

即确定了xy边的状态,可以得到对应的格子左边和上边的状态



(红色为dp[ i ][ j ]轮廓线,蓝色为dp[ i ][ j - 1 ]轮廓线)

观察序号之间的关系可以发现

dp[ i ][ j ][ k1 ] 要找对应的左边和上边状态

即找dp[ i ][ j - 1 ][ k2 ],使得k1和k2,除了x和y位,其他位都一样,所以可以由k1状态通过位运算轻松表示出对应的k2状态


还需注意,初始化 dp[i][0]时,dp[i][0][k << 1] = dp[i - 1][m][k];

可由下图看出



代码:

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
#define MOD 1000000007
#define INF 0x7fffffff
using namespace std;
typedef long long ll;
int cell[15][15];
ll dp[15][15][5005];

int main()
{
    #ifdef LOCAL
    freopen("dpdata.txt", "r", stdin);
    #endif

    int t, n, m;
    scanf("%d", &t);
    for(int cas = 1; cas <= t; cas++)
    {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= m; j++)
                scanf("%d", &cell[i][j]);
        memset(dp, 0, sizeof(dp));
        dp[0][m][0] = 1;
        for(int i = 1; i <= n; i++)
        {
            for(int k = 0; k < (1 << m); k++)
            { //初始化决策到第i行第0格时的状态
                dp[i][0][k << 1] = dp[i - 1][m][k];
            }
            for(int j = 1; j <= m; j++)
            {
                for(int k = 0; k < (1 << (m + 1)); k++)
                {
                    int y = 1 << j, x = 1 << (j - 1);
                    if(cell[i][j])
                    { //这格可到达
                        if((k & x) && (k & y))
                            dp[i][j][k] = dp[i][j - 1][k - x - y];
                        else if(!(k & x) && !(k & y))
                            dp[i][j][k] = dp[i][j - 1][k + x + y];
                        else //k^x^y异或运算使得x和y插了变成不插,没插变成插
                            dp[i][j][k] = dp[i][j - 1][k] + dp[i][j - 1][k ^ x ^ y];
                    }
                    else
                    {
                        if(!(k & x) && !(k & y))
                            dp[i][j][k] = dp[i][j - 1][k];
                        else
                            dp[i][j][k] = 0;
                    }
                }
            }
        }
        printf("Case %d: There are %I64d ways to eat the trees.\n", cas, dp[n][m][0]);
    }

    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值