POJ1185 状压dp(二进制//三进制)解法
很显然这是一道状压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(二进制//三进制)解法的更多相关文章
- hdu3001(状压dp,三进制)
		
Travelling Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total ...
 - HDU3001 Travelling —— 状压DP(三进制)
		
题目链接:http://acm.split.hdu.edu.cn/showproblem.php?pid=3001 Travelling Time Limit: 6000/3000 MS (Java/ ...
 - BZOJ 1688: [Usaco2005 Open]Disease Manangement 疾病管理 状压DP + 二进制 + 骚操作
		
#include <bits/stdc++.h> #define setIO(s) freopen(s".in","r",stdin) #defin ...
 - bzoj 5299: [Cqoi2018]解锁屏幕 状压dp+二进制
		
比较简单的状压 dp,令 $f[S][i]$ 表示已经经过的点集为 $S$,且最后一个访问的位置为 $i$ 的方案数. 然后随便转移一下就可以了,可以用 $lowbit$ 来优化一下枚举. code: ...
 - 【CSP模拟赛】Adore(状压dp 二进制)
		
题目描述 小w偶然间见到了一个DAG.这个DAG有m层,第一层只有一个源点,最后一层只有一个汇点,剩下的每一层都有k个节点.现在小w每次可以取反第i(1<i<n-1)层和第i+1层之间的连 ...
 - hdu 3001(状压dp, 3进制)
		
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3001 由于本题中一个点最多能够访问2次,由此可以联想到3进制; visited[i][j]表示在状态i ...
 - Travelling(HDU3001+状压dp+三进制+最短路)
		
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3001 题目: 题意:n个城市,m条边,每条边都有一个权值,问你经过所有的城市且每条边通过次数不超过两次 ...
 - hdu 3001 Travelling 经过所有点(最多两次)的最短路径 三进制状压dp
		
题目链接 题意 给定一个\(N\)个点的无向图,求从任意一个点出发,经过所有点的最短路径长度(每个点至多可以经过两次). 思路 状态表示.转移及大体思路 与 poj 3311 Hie with the ...
 - P1433 吃奶酪(洛谷)状压dp解法
		
嗯?这题竟然是个绿题. 这个题真的不(很)难,我们只是不会计算2点之间的距离,他还给出了公式,这个就有点…… 我们直接套公式去求出需要的值,然后普通的状压dp就可以了. 是的状压dp. 这个题的数据加 ...
 
随机推荐
- Scrum Meeting 7
			
第七次会议 No_00:工作情况 No_01:任务说明 待完成 已完成 No_10:燃尽图 No_11:照片记录 待更新 No_100:代码/文档签入记录 No_101:出席表 ...
 - Linux内核分析:期中总结
			
第一章:计算机是如何工作的 计算机大部分都是用冯诺依曼体系结构,即存储程序计算机. 两个层面: 1.硬件: cpu(IP寄存器:指针,指向内存的某块区域)——总线——内存(代码与数据) 2.程序员: ...
 - Linux内核分析第一次学习报告
			
Linux内核分析第一次学习报告 学生 黎静 学习内容 1.存储程序计算机工作模型 冯诺依曼体系结构:核心思想为存储程序计算机. CPU抽象为for循环,总是执行下一条指令,内存保存指令和数据,CPU ...
 - 第三个spring冲刺第3天
			
基本功能跟界面都完成了,今天小组开了个会,基于跟别的小组对比的效果,感觉自己组的效果没别人的好,很多方面还欠缺,所以我们会继续跟进完善.
 - Metrics.NET step by step使用Metrics监控应用程序的性能
			
使用Metrics监控应用程序的性能 在编写应用程序的时候,通常会记录日志以便事后分析,在很多情况下是产生了问题之后,再去查看日志,是一种事后的静态分析.在很多时候,我们可能需要了解整个系统在当前,或 ...
 - 用delete和trancate删除表记录的区别
			
首先说相同点,就是他们都能删除表中的数据,区别有两点: 第一点: delete语句在删除记录的时候可以有选择的删除某些数据(使用where子句),当然,如果不添加where子句,就是删除所有记录 而t ...
 - SQLSERVER 2014 内存优化表相关
			
更新了SP2的补丁能够解决 不能收缩日志文件的bug了. 但是因为已经不用内存优化表了, 所以想着能够删除内存优化表的file group 但是发现 很难删除 先说结论: 以下是针对内存优化文件组的 ...
 - HTTP协议整理
			
一.概念 1.HTTP协议:即超文本传输协议(Hypertext transfer protocol).是一种详细规定了浏览器和Web服务器之间互相通信的规则,它允许将超文本标记语言(HTML)文档从 ...
 - pandas设置值、更改值
			
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2018/5/24 15:03 # @Author : zhang chao # @Fi ...
 - 使用DataContext和ItemsSource将数据源绑定到ListView上的区别
			
在最近的一个项目中,将DataView类型的数据源绑定到ListView控件时,发现当DataView的内容发生变化时,前台的ListView控件的内容并没有发生改变,在这里我先贴出前台要绑定数据源的 ...