原题地址说实话我第一次尝试写炮兵阵地是在2009年……已经过去两年多了,终于找到了一个好的解法……庆祝一下……
【状态压缩DP】
所谓状态压缩DP,就是对于某些DP问题,每一阶段的状态都有很多维,要利用某些手段将它们压缩到一维(一个正整数),顺便进行状态的精简(去掉不合法的状态),然后再进行DP。这里讲的主要是传统的状压DP,有一种基于“插头”的DP,更高级,以后再搞。
对于本题,可以设计出一个这样的状态:[0..1][0..1][0..1]...[0..1](有M个[0..1]),表示该行的每个格子放不放炮兵,如果放,为1,否则为0。显然,这是一个M位二进制数,如果能把它们压缩成一个int就好了。
【如何压缩】
第一个问题是这么多维的状态如何压缩的问题。
对于本题,由于是二进制数,直接压缩就可以了。但是对于某些情况,状态是个三进制数(每个格子的属性都有3种)甚至更多进制数,这样,直接压缩会导致无法使用位运算,从而使“解压”变得很麻烦,耗时也很长(需要暴力),因此,可以将每位三进制都拆成两位二进制:
0->00
1->01
2->10
(当然1拆成10、2拆成11也木有问题,只要能区分开就行了)
这样,每个状态就可以用2个二进制数来表示,可以在构造出所有合法状态以后将每个状态所对应的两位二进制数存到struct里面,使用时调出即可。
【如何精简】
这个问题是最重要的,因为,如果不精简,在枚举状态以及转移的时候就会枚举到很多不合法状态,导致时间浪费。
所谓精简,是指在预处理以及DP过程中,尽量避开不合法状态。
(1)预处理中的精简:
包括3个部分:
<1>找到所有可能的合法状态并编号:根据题意限制,有的状态在阶段内就不合法(比如本题,一行一阶段,那么凡是有两个1位的距离小于2的状态都不合法),而且这种状态所占的比重往往还很大(本题中,M=10时,也只有60种可能的合法状态),此时,为了找到这些合法状态,可以DFS构造实现。
需要注意的是,有的题不光要找到一个阶段内的合法状态,还要找到两个或两个以上阶段内的合法状态(比如那个有关多米诺骨牌的题),此时需要两个int同时DFS;
在找到合法状态以后,需要对每个合法状态进行编号,以达到“压缩”的目的。这里就涉及到了状态编号和状态表示的问题:比如,状态1001,表示为9,在DFS中第一个被搜到,因此编号为0,不要搞混了这两个(尤其不要搞混“编号为0”和“状态表示为0”,它们是不同的)。在预处理和DP的过程中,所有涉及到状态的数组下标,全部是编号而不是表示,知道编号要求表示,可以在DFS中记录的数组里面调,而知道表示要求编号,可以利用逆数组或者哈希;
<2>找到每一阶段的合法状态:即使是<1>中被判定为合法的状态,在具体的各个阶段中也未必合法(比如本题,如果某一行的某一个位置是'H',不能放,而某一个状态在这里放了,则不合法),因此要对每个阶段再枚举一遍,找到真正合法的状态,并计入一个vector;
<3>找到状态转移中的合法状态:在状态转移中,往往要求状态不冲突(比如本题,在连续的三个阶段中,都不能有某一位有两个为1的情况),因此,还要枚举每个状态在转移时与其不冲突的状态,并计入vector。
注意,有时候这一步不是很容易进行,需要在DP过程中进行;
(2)DP过程中的精简:
DP过程中,枚举状态、转移决策都只枚举合法的,在vector里面调(注意vector里记录的全都是状态编号而不是表示!),可以大大减少枚举量,不过有时候,还会有一些时间浪费,这时候,可以采取一些其它的办法来精简,比如再次进行DFS构造合法状态等。
总之,这类问题的目标就是“精简,精简,再精简,使枚举到的不合法状态减到最少”。
代码:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
using namespace std;
#define re(i, n) for (int i=0; i<n; i++)
#define re1(i, n) for (int i=1; i<=n; i++)
#define re2(i, l, r) for (int i=l; i<r; i++)
#define re3(i, l, r) for (int i=l; i<=r; i++)
#define rre(i, n) for (int i=n-1; i>=0; i--)
#define rre1(i, n) for (int i=n; i>0; i--)
#define rre2(i, r, l) for (int i=r-1; i>=l; i--)
#define rre3(i, r, l) for (int i=r; i>=l; i--)
#define pb push_back
#define IR iterator
typedef vector <int> vi;
const int MAXN = 103, MAXM = 11, MAXS = 100, INF = ~0U >> 2;
int n, m, S, A[MAXN], B[MAXS], T1[MAXS], F[MAXN][MAXS][MAXS], res;
bool F0[MAXN][MAXS];
vi P0[MAXN], P1[MAXS][MAXS];
void init()
{
scanf("%d%d", &n, &m); getchar();
re1(i, n) {A[i] = 0; re(j, m) A[i] |= ((getchar() == 'P') << j); getchar();}
}
void dfs(int v, int ST)
{
if (v >= m) B[S++] = ST; else {dfs(v + 3, ST | (1 << v)); dfs(v + 1, ST);}
}
void prepare()
{
S = 0; dfs(0, 0);
re(i, S) {T1[i] = 0; for (int j=B[i]; j; j-=j&(-j)) T1[i]++;}
re1(i, n) re(j, S) if (!(~A[i] & B[j])) {P0[i].pb(j); F0[i][j] = 1;} P0[0].pb(S - 1); F0[0][S - 1] = 1;
re(i, S) re(j, S) if (!(B[i] & B[j])) re(k, S) if (!(B[i] & B[k]) && !(B[j] & B[k])) P1[i][j].pb(k);
}
void solve()
{
re3(i, 0, n) re(j1, S) re(j2, S) F[i][j1][j2] = -INF; F[0][S - 1][S - 1] = 0;
vi::IR vi_e0, vi_e1, vi_e2; int j0, j1, k, V;
re(i, n) {
vi_e0 = P0[i].end(); if (i) vi_e1 = P0[i - 1].end(); else vi_e1 = P0[i].end();
for (vi::IR p=P0[i].begin(); p != vi_e0; p++)
for (vi::IR p_=P0[i ? i - 1 : i].begin(); p_ != vi_e1; p_++) {
j0 = *p; j1 = *p_;
if (!(B[j0] & B[j1])) {
vi_e2 = P1[j0][j1].end();
for (vi::IR p__ = P1[j0][j1].begin(); p__ != vi_e2; p__++) {
k = *p__;
if (F0[i + 1][k]) {
V = F[i][j0][j1] + T1[k];
if (V > F[i + 1][k][j0]) F[i + 1][k][j0] = V;
}
}
}
}
}
res = 0; re(i, S) re(j, S) if (F[n][i][j] > res) res = F[n][i][j];
}
void pri()
{
printf("%d\n", res);
}
int main()
{
init();
prepare();
solve();
pri();
return 0;
}