二维状压DP经典题

炮兵阵地

题目链接

题目大意:在n*m的地图上放置炮兵,每个炮兵的攻击范围是上下左右两格内,有两种不同的地形,山地(用“H” 表示),平原(用“P”表示),只有平原可以布置炮兵,在不冲突的前提下最多可以布置多少炮兵?

这道题非常经典,我们用dp[i] [j] [k]表示第i行在第j种选取状态下,第i-1行在第k种选取状态下前i行最多摆放的炮兵数量。然后我们首先预处理每一行所有的合法状态,以降低时间复杂度。用num[i]表示第i行的合法状态数量,state[i] [j]表示第i行的第j种合法状态是什么,用temp[i]存储第i行的初始状态,用c[i]存储每种合法状态对应的炮兵数量。

可以不处理直接枚举所有状态吗

如果不预先处理合法状态,那么每一行所有的状态可能有2^10 = 1024种,由于炮兵的摆放需要考虑前两行的状态,那么三个循环枚举状态就会达到惊人的时间复杂度,所以预处理很关键!进行预处理我们会发现每一行的合法状态最多60种,这样即使是3个循环复杂度也会很低。

其他细节都在代码里了。

#include <bits/stdc++.h>
using namespace std;
int n,m,ans;
int dp[105][65][65];
int num[105],temp[105];
int state[105][65];
int c[1 << 10 + 5];
int count(int x){
    int sum = 0;
    while(x){
        if(x & 1) sum++;
        x >>= 1;
    }
    return sum;
}
int main(){
    cin >> n >> m;
    //每行的合法状态最多只有60种!
    // int p = 0;
    // for(int i = 0;i< (1 << 10);i++){
    //     int now = i;
    //     if((now & (now >> 1)) == 0 && (now & (now >> 2)) == 0) p++;
    // }
    // cout << p << endl;
    for(int i = 1;i <= n;i++){
        string s;
        cin >> s;
        for(int j = 0;j < m;j++){
            if(s[j] == 'P') temp[i] += (1 << j);
        }
    }
    //对第0行特殊处理
    state[0][++num[0]] = 0;
    //预处理合法状态
    for(int i = 0;i < (1 << m);i++){
        for(int j = 1;j <= n;j++){
            int now = i;
            if(!(now & (now >> 1)) && !(now & (now >> 2))
               && (now | temp[j]) == temp[j]){
                state[j][++num[j]] = i;
                c[i] = count(i);
            }
        }
    }
    for(int i = 1;i <= n;i++){
        for(int j = 1;j <= num[i];j++){
            int now = state[i][j];
            //对第一行特殊处理,防止越界
            if(i == 1) {
                dp[i][j][1] = max(dp[i][j][1],c[now]);
                continue;
            }
            for(int k = 1;k <= num[i - 1];k++){
                int pre = state[i - 1][k];
                if(!(now & pre)){
                    for(int l = 1;l <= num[i - 2];l++){
                        int pree = state[i - 2][l];
                        if(!(now & pree) && !(pre & pree)){
                            dp[i][j][k] = max(dp[i][j][k],dp[i - 1][k][l] + c[now]);
                        }
                    }
                }
            }
        }
    }
    for(int i = 1;i <= num[n];i++){
        for(int j = 1;j <= num[n - 1];j++){
            ans = max(ans,dp[n][i][j]);
        }
    }
    cout << ans << endl;
    return 0;
}

排兵布阵 HDU4539

题目链接

大意:一个n*m的平原布置士兵。每个士兵可以攻击到并且只能攻击到与之曼哈顿距离为2的位置以及士兵本身所在的位置。当然,一个士兵不能站在另外一个士兵所能攻击到的位置,同时因为地形的原因也不是每一个位置都可以安排士兵。 (输入中1可以布置,0不可以布置)问:最多能安排多少个士兵。

思路和上一题基本一样,换汤不换药.

#include<bits/stdc++.h>
using namespace std;
int n,m,ans;
int state[105],num[170],c[1 << 10 + 5],legal[105][170],dp[105][170][170];
int count1(int x){
    int sum = 0;
    while(x){
        if(x & 1) sum++;
        x >>= 1;
    }
    return sum;
}
bool ok(int now,int row){
    return (now & (now >> 2)) == 0 && (now | state[row]) == state[row];
}
bool check(int now,int pre){
    return (now & (pre >> 1)) == 0 && (now & (pre << 1)) == 0;
}
bool recheck(int now,int pree){
    return (now & pree) == 0;
}
//由于是多组数据,所以每次都要初始化!
void init(){
    ans = 0;
    memset(num,0,sizeof(num));
    memset(dp,0,sizeof(dp));
    memset(state,0,sizeof(state));
}
int main()
{
    memset(c,-1,sizeof(c));
    while(~scanf("%d %d",&n,&m)){
        init();
        for(int i = 1;i <= n;i++){
            for(int j = 0;j < m;j++){
               int x;
               scanf("%d",&x);
               if(x) state[i] |= (1 << j);
            }
        }
        //预处理合法状态
        legal[0][++num[0]] = 0;
        for(int i = 0;i < (1 << m);i++){
            for(int j = 1;j <= n;j++){
                if(ok(i,j)) {
                    legal[j][++num[j]] = i;
                    if(c[i] == -1) c[i] = count1(i);
                }
            }
        }
        for(int i = 1;i <= n;i++){
            for(int j = 1;j <= num[i];j++){
                int s1 = legal[i][j];
                if(i == 1) {
                    dp[i][j][1] = max(dp[i][j][1],c[s1]);
                    continue;
                }
                for(int k = 1;k <= num[i - 1];k++){
                    int s2 = legal[i - 1][k];
                    if(check(s1,s2)){
                        for(int l = 1;l <= num[i - 2];l++){
                            int s3 = legal[i - 2][l];
                            if(recheck(s1,s3)){
                                dp[i][j][k] = max(dp[i][j][k],dp[i-1][k][l] + c[s1]);
                            }
                        }
                    }
                }
            }
        }
        for(int i = 1;i <= num[n];i++){
            for(int j = 1;j <= num[n - 1];j++){
                ans = max(ans,dp[n][i][j]);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

原文地址:https://www.cnblogs.com/Rui-Roman/p/11400238.html

时间: 2024-11-10 14:43:00

二维状压DP经典题的相关文章

POJ 2411 &amp;&amp; HDU 1400 Mondriaan&#39;s Dream (状压dp 经典题)

Mondriaan's Dream Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 12341   Accepted: 7204 Description Squares and rectangles fascinated the famous Dutch painter Piet Mondriaan. One night, after producing the drawings in his 'toilet series

POJ 1185 炮兵阵地 (状压dp 经典中的经典)

炮兵阵地 Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 21381   Accepted: 8290 Description 司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队.一个N*M的地图由N行M列组成,地图的每一格可能是山地(用"H" 表示),也可能是平原(用"P"表示),如下图.在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队):一支炮兵部队在地图上的攻击

二:状压dp

一:状压dp的基本特征 状态压缩问题一般是指用十进制的数来表示二进制下的状态 这种用一个数来表示一组数,以降低表示状态所需的维数的解题手段,就叫做状态压缩. 常用到位运算 二:位运算 &:与运算,相同为1不同为0 | :或运算,全0位0,否则为1 ^:异或运算,不同为1,相同为0(也叫半加与运算) <<:左移操作,x<<j,x在二进制下向左移j位,(也就是原来的数乘j即:x*=j)右边用0填充,反码不同,要用1填充(负数) >>:右移操作,x>>j相

hdu 1185 状压dp 好题 (当前状态与上两行有关系)

/* 状压dp 刚开始&写成&&看了好长时间T0T. 状态转移方程 dp[i][k][j]=Max(dp[i][k][j],dp[i-1][l][k]+num[i][j]);(第i行的第j个状态有上一行的第k个状态得到) num[i][j]有两个功能,第一:判断第i行第j个状态是否合法 第二:判断第i行第j个状态的数目 */ #include<stdio.h> #include<string.h> #define N 110 int dp[N][N][N];

【状压DP水题】[USACO06NOV]玉米田Corn Fields

题目描述 Farmer John has purchased a lush new rectangular pasture composed of M by N (1 ≤ M ≤ 12; 1 ≤ N ≤ 12) square parcels. He wants to grow some yummy corn for the cows on a number of squares. Regrettably, some of the squares are infertile and can't b

POJ 2411 状压DP经典

Mondriaan's Dream Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 16771   Accepted: 9683 Description Squares and rectangles fascinated the famous Dutch painter Piet Mondriaan. One night, after producing the drawings in his 'toilet series

poj 3254 状压dp入门题

1.poj 3254  Corn Fields    状态压缩dp入门题 2.总结:二进制实在巧妙,以前从来没想过可以这样用. 题意:n行m列,1表示肥沃,0表示贫瘠,把牛放在肥沃处,要求所有牛不能相邻,求有多少种放法. #include<iostream> #include<cstring> #include<cmath> #include<queue> #include<algorithm> #include<cstdio> #d

[状压dp]经典TSP

0出发 每个顶点经过一次 回到0 最小花费. 记忆化搜索: 1 // s: 已经访问过的节点状态 v: 当前位置 2 int dfs(int s, int v) 3 { 4 if(dp[s][v]>=0) 5 return dp[s][v]; 6 if(s==(1<<n)-1 && v==0) // 所有都走过 并 回到0 7 return dp[s][v]=0; 8 int ans=INF; 9 for(int u=0;u<n;u++) 10 if(!(s>

URAL 1152 Faise Mirrors 状压DP 简单题

1152. False Mirrors Time limit: 2.0 secondMemory limit: 64 MB Background We wandered in the labyrinth for twenty minutes before finally entering the large hall. The walls were covered by mirrors here as well. Under the ceiling hung small balconies wh