HDU 4539 郑厂长系列故事——排兵布阵 (状压思维题)*

本文深入解析了一种使用状态压缩的动态规划(DP)方法,通过解决一个具体的编程竞赛题目,展示了如何有效地利用位运算和状态转移方程来优化算法。文章详细解释了状态压缩的概念,如何找出合法状态并进行枚举,以及如何应用DP技巧来解决复杂问题。

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

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4539

#include<bits/stdc++.h>
using namespace std;
#define debug puts("YES");
#define rep(x,y,z) for(int (x)=(y);(x)<(z);(x)++)
#define read(x,y) scanf("%d%d",&x,&y)
#define ll long long
#define lrt int l,int r,int rt
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define root l,r,rt
const int  maxn =1e6+5;
const int mod=1e9+7;
const int ub=1e6;
ll powmod(ll x,ll y){ll t; for(t=1;y;y>>=1,x=x*x%mod) if(y&1) t=t*x%mod; return t;}
ll gcd(ll x,ll y){return y?gcd(y,x%y):x;}
int n,m,x,row[110];///每一行的状态
int s[1<<11],cnt[1<<11];///合法状态和可以排布的士兵数量
int dp[110][220][220];
int getcnt(int x){int ret=0;while(x>0) x-=(x&(-x)),ret++;return ret;}
/*
中文题目。

状态压缩,
先找出状态转移方程,
dp[i][j][k]=max(dp[i-1][k][p]): 枚举p。
代表在第i行状态为j,第i-1行状态为k时的最大值。
因为我们可以看出当相距三行及以上时状态是独立的,
所以我们需要用DP暴力枚举当前状态的前两行。

那么下面就是状态压缩和位运算技巧了。
首先为了判定方便要把0压缩成1,因为我们答案要枚举的是
不含零的子集,所以把0压成1就可以用与运算直接判断是否符合要求。

我们对于每行先把符合要求的状态压出来,然后直接在符合要求的状态中进行枚举,
符合的条件是相邻间距不能有刚好为2的。
四层循环,后三层每一层都有条件,第二层是枚举当前row的1的子集即可,
第三层是i层与i-1层符合要求,就是不能有刚好错位的。
第四层是要求不能有和i层状态重复的,这很容易判得(不用麻烦的判定和i-1层的关系)
这也是状态转移的技巧吧。
*/

int main()
{
   while(~scanf("%d%d",&n,&m))
   {
        for(int i=0;i<n;i++)
        {
            row[i]=0;
            for(int j=0;j<m;j++)
            {
                scanf("%d",&x);
                if(!x) row[i]=(row[i]<<1)|1;///把状态反过来这样方便点
                else row[i]<<=1;
            }
        }

        int idx=0,ans=0;
        for(int i=0;i<(1<<m);i++)
        {
            if(i&(i<<2)) continue;
            s[idx]=i,cnt[idx++]=getcnt(i);
        }

        memset(dp,0,sizeof(dp));///初始化为0还是必要的
        for(int i=0;i<idx;i++)
        {
            if(s[i]&row[0]) continue;
            dp[0][i][0]=cnt[i];
        }///初始化DP状态数组

        for(int i=1;i<n;i++)
        {
            for(int j=0;j<idx;j++)
            {
                if(s[j]&row[i]) continue;///必须是完全符合的才行
                for(int k=0;k<idx;k++)
                {
                    if( (s[j]&(s[k]>>1)) || (s[j]&(s[k]<<1)) ) continue;///
                    for(int p=0;p<idx;p++)
                    {
                        if( s[j]&s[p] ) continue;///有一个相同位都不行
                        dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][p]+cnt[j]);///状态转移方程
                    }
                }
            }
        }
        for(int i=0;i<idx;i++) for(int j=0;j<idx;j++) ans=max(ans,dp[n-1][i][j]);
        printf("%d\n",ans);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值