很显然这是一道状压dp的题目

由于每个最优子结构和前两行有关,一个显而易见的想法是用三维dp[i][j][k]用来记录在第i行下为j状态,i - 1行为k状态时的最大值,然而dp[100][1 << 11][1 << 11]显然是要MLE的,我们可以想到用滚动数组优化,事实上确实可以用滚动数组优化。然而 在时间复杂度上 100 * 1024 * 1024 * 1024也是一个不可能补TLE的数字,一个不那么显然的办法是预处理出所有可行的状态,经过看题解或者写个暴力炸一下之后可以知道这些状态并不超过70,也就是说时间复杂度可以优化到100 * 70^3,这就看起来很合情合理了,数组也不用上滚动数组直接跑就好了。

剩下的就是实现的问题了。

用一个state数组预处理出所有的合法状态(在不考虑高地不高地的情况下)

用一个base数组预处理出所有高地的状态(高地为1,平地为0)当state中的状态 & 上base中的状态不为0时,代表有一个小兵站在了高地上,这是不被允许的,就要跳过这个状态。

用一个solider数组预处理出所有合法状态下的小兵数目,左右是省的每次都计算一下有几个小兵,不但让这个程序跑起来很快,也让我们看起来很帅。

附上这个解决方法的代码。

#include <map>
#include <set>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <functional>
#define For(i, x, y) for(int i=x; i<=y; i++)
#define _For(i, x, y) for(int i=x; i>=y; i--)
#define Mem(f, x) memset(f, x, sizeof(f))
#define Sca(x) scanf("%d", &x)
#define Scl(x) scanf("%lld",&x);
#define Pri(x) printf("%d\n", x)
#define Prl(x) printf("%lld\n",x);
#define CLR(u) for(int i = 0; i <= N ; i ++) u[i].clear();
#define LL long long
#define ULL unsigned long long
#define mp make_pair
#define PII pair<int,int>
#define PIL pair<int,long long>
#define PLL pair<long long,long long>
#define pb push_back
#define fi first
#define se second
using namespace std;
typedef vector<int> VI;
const double eps = 1e-;
const int maxn = ;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + ;
inline int read()
{
int now=;register char c=getchar();
for(;!isdigit(c);c=getchar());
for(;isdigit(c);now=now*+c-'',c=getchar());
return now;
}
int N,M;
char MAP[maxn][];
int state[maxn]; //所有合法状态
LL dp[][maxn][maxn]; //在i行第j状态以及i- 1行第k状态下的最大值
LL solider[maxn]; //在这个状态下的士兵
int base[maxn]; // 原地图的的第i个原状态
int cnt; //合法状态的数目
int main()
{
while(~scanf("%d%d",&N,&M)){
Mem(base,); Mem(solider,); Mem(state,); Mem(dp,);
cnt = ;
For(i,,N){
scanf("%s",&MAP[i]);
// cout << MAP[i] << endl;
for(int j = ; j < M ; j ++){
if(MAP[i][j] == 'H') base[i] += << j;
}
}
for(int i = ; i < << M; i ++){
if((i & (i << )) || (i & (i << ))) continue;
state[++cnt] = i;
int k = i;
while(k){
solider[cnt] += k & ;
k >>= ;
}
}
For(i,,cnt){
// cout << solider[i] << endl;
if(base[] & state[i]) continue;
dp[][i][] = solider[i];
}
For(i,,cnt){
if(base[] & state[i]) continue;
For(j,,cnt){
if(base[] & state[j] || state[i] & state[j]) continue;
dp[][i][j] = max(dp[][j][] + solider[i],dp[][i][j]);
}
}
For(i,,N){
For(j,,cnt){
if(base[i] & state[j]) continue;
For(k,,cnt){
if(base[i - ] & state[k] || state[j] & state[k]) continue;
For(p,,cnt){
if(base[i - ] & state[p] || state[p] & state[k] || state[j] & state[p]) continue;
dp[i & ][j][k] = max(dp[i & ][j][k],dp[i + & ][k][p] + solider[j]);
}
}
}
}
LL MAX = ;
For(i,,cnt){
For(j,,cnt){
MAX = max(MAX,dp[N & ][i][j]);
}
}
Prl(MAX);
}
return ;
}

事实上除了以上这种巧妙地方法之外,我们依然有更暴力但是却更难写的方法,就是将二进制状态压缩改为三进制的状态压缩。

我们假设在放置一个小兵之后会产生一个“缓冲带”,导致下面的这个状态变为2,下下面的状态变为1,再下面变回0,意味着缓冲区结束,这里可以继续放置小兵。但是仔细一想发现这样构成的状态并不是那么好写状态转移方程,我们从记忆话搜索里得到灵感,考虑直接dfs暴搜。

由于经过了状态压缩,dfs的状态转移并不那么困难,我们用一个整数cur来表示此时的状态,用一个next来表示下一行的状态,

每次的转移主要是横向的转移,当到了行末尾的时候转移到下一行,此时cur的值变为next,next的值变为0,到最后一行时开始返回,更新回答案。

像这样的状态表示比较复杂,冗余的不合法状态较多的题目,不一定要写出确切的状态转移方程,而用dfs也可以很好的解决问题,不过在这题上的效率并不是很理想,上面的400ms,这个1600ms,主要提供遇到问题的解决思路。

#include <map>
#include <set>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <functional>
#define For(i, x, y) for(int i=x; i<=y; i++)
#define _For(i, x, y) for(int i=x; i>=y; i--)
#define Mem(f, x) memset(f, x, sizeof(f))
#define Sca(x) scanf("%d", &x)
#define Scl(x) scanf("%lld",&x);
#define Pri(x) printf("%d\n", x)
#define Prl(x) printf("%lld\n",x);
#define CLR(u) for(int i = 0; i <= N ; i ++) u[i].clear();
#define LL long long
#define ULL unsigned long long
#define mp make_pair
#define PII pair<int,int>
#define PIL pair<int,long long>
#define PLL pair<long long,long long>
#define pb push_back
#define fi first
#define se second
using namespace std;
typedef vector<int> VI;
const double eps = 1e-;
const int maxn = ;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + ;
inline int read()
{
int now=;register char c=getchar();
for(;!isdigit(c);c=getchar());
for(;isdigit(c);now=now*+c-'',c=getchar());
return now;
}
int N,M;
int dp[maxn][];
char MAP[maxn][];
int power[]={,,,,,,,,,};
int getbit(int i,int pos){
if(pos == ) return i % ;
if(pos >= M) return ;
if(i >= power[pos]){
return (i / power[pos]) % ;
}
return ;
}
//x,y为横纵坐标,cur为上两行的状态,next为下一状态,cnt为记录x行已放置的
void dfs(int x,int y,int cur,int next,int cnt)
{
if(!y){ //刚进入当前行
if(dp[x][cur] != -) return;
dp[x][cur] = ;
}
if(y >= M){ //已经到行末尾
if(x < N - ){
dfs(x + ,,next,,); //转变为下一行,下一行状态转变为当前状态,下一行状态初始化为0
dp[x][cur] = max(dp[x][cur],cnt + dp[x + ][next]); //从上一个状态更新这个状态
}else{
dp[x][cur] = max(dp[x][cur],cnt); //由于没有下一个状态,这个状态的最大值就是他自己
}
return;
}
int i = getbit(cur,y); //这个点的值
if(!i && MAP[x][y] == 'P'){ //这个点可放小兵
int j = * power[y],k; //在这个点放了小兵之后next要增加的值,也就是下边增加一个2
k = getbit(cur,y + );
if(k == ){ //由于这个点的右边上面刚放过一个小兵,右边要增加1
j += power[y + ];
}
k = getbit(cur,y + ); //同理这个点右边的右边的上面放过一个小兵
if(k == ){
j += power[y + ];
}
dfs(x,y + ,cur,next + j,cnt + ); //这个点放了小兵
dfs(x,y + ,cur,next,cnt); //这个点不放小兵
return;
}
if(i == ) dfs(x,y + ,cur,next + power[y],cnt); //下面为1
else dfs(x,y + ,cur,next,cnt); //下面为0
}
int main()
{
while(~scanf("%d%d",&N,&M)){
for(int i = ; i < N ; i ++){
scanf("%s",MAP[i]);
}
Mem(dp,-);
dfs(,,,,);
Pri(dp[][]);
}
return ;
}

POJ1185 状压dp(二进制//三进制)解法的更多相关文章

  1. hdu3001(状压dp,三进制)

    Travelling Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total ...

  2. HDU3001 Travelling —— 状压DP(三进制)

    题目链接:http://acm.split.hdu.edu.cn/showproblem.php?pid=3001 Travelling Time Limit: 6000/3000 MS (Java/ ...

  3. BZOJ 1688: [Usaco2005 Open]Disease Manangement 疾病管理 状压DP + 二进制 + 骚操作

    #include <bits/stdc++.h> #define setIO(s) freopen(s".in","r",stdin) #defin ...

  4. bzoj 5299: [Cqoi2018]解锁屏幕 状压dp+二进制

    比较简单的状压 dp,令 $f[S][i]$ 表示已经经过的点集为 $S$,且最后一个访问的位置为 $i$ 的方案数. 然后随便转移一下就可以了,可以用 $lowbit$ 来优化一下枚举. code: ...

  5. 【CSP模拟赛】Adore(状压dp 二进制)

    题目描述 小w偶然间见到了一个DAG.这个DAG有m层,第一层只有一个源点,最后一层只有一个汇点,剩下的每一层都有k个节点.现在小w每次可以取反第i(1<i<n-1)层和第i+1层之间的连 ...

  6. hdu 3001(状压dp, 3进制)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3001 由于本题中一个点最多能够访问2次,由此可以联想到3进制; visited[i][j]表示在状态i ...

  7. Travelling(HDU3001+状压dp+三进制+最短路)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3001 题目: 题意:n个城市,m条边,每条边都有一个权值,问你经过所有的城市且每条边通过次数不超过两次 ...

  8. hdu 3001 Travelling 经过所有点(最多两次)的最短路径 三进制状压dp

    题目链接 题意 给定一个\(N\)个点的无向图,求从任意一个点出发,经过所有点的最短路径长度(每个点至多可以经过两次). 思路 状态表示.转移及大体思路 与 poj 3311 Hie with the ...

  9. P1433 吃奶酪(洛谷)状压dp解法

    嗯?这题竟然是个绿题. 这个题真的不(很)难,我们只是不会计算2点之间的距离,他还给出了公式,这个就有点…… 我们直接套公式去求出需要的值,然后普通的状压dp就可以了. 是的状压dp. 这个题的数据加 ...

随机推荐

  1. SCRUM 12.18

    明天就是编译课设的第二次中期考核了,大家都感到有一些压力. 所以我们决定今天减少一些工作量. 工作任务分配依旧如往常 成员 任务 彭林江 落实API 郝倩 研究遍历美团数据方法 牛强 落实意见反馈功能 ...

  2. <<梦断代码>>阅读笔记二

    这是第二篇读书笔记,这本书我已经读了有一大半了,感觉书中所描述的人都是疯子,一群有创造力,却又耐得住寂寞的疯子. 我从书中发现几点我比较感兴趣的内容. 第一个,乐高之梦.将程序用乐高积木一样拼接起来. ...

  3. jisuanqi

    1.jisuanqi 2.https://github.com/12wangmin/text/tree/master 3.计算截图 7+8 清除 4.总结 通过课程设计,主要要达到两个目的,一是检验和 ...

  4. 腾讯 xtestserver 基本使用教程~

    刚刚简单录制了下 腾讯demo的基本测试脚本 成功~get新技能成功~开心ing~ 体验就是: 1.各种安卓机找开发者中心选项的usb调试模式太难找了.. 2.不管录制还是播放录制时都感觉好慢... ...

  5. mybatis分页 -----PageHelper插件

    对查询结果进行分页 一,使用limit进行分页 1.mybatis 的sql语句: <if test="page !=null and rows !=null"> li ...

  6. Mybatis Update statement Date null

    Mybatis Update statement Date null 只要在Model里把字段置为java的null即可.

  7. 使用非服务器磁盘(MBROnly)安装ESXi时的方法.

    From ESXi 5.0, if you install ESXi to a empty hard disk, the target disk will be prepared with GPT-b ...

  8. [转帖] infiniband的协议速度

  9. IDEA 操作及快捷键总结

    一.设置IDEA使用Eclipse快捷键 File->Settings->Keymap->选择Eclipse,就可以使用Eclipse的快捷键了,但是不能修改.如果想要修改,需要点击 ...

  10. Node http和express和mysql

    const http = require("http");const express = require("express");const mysql = re ...