题解 AT2669 【[AGC017F] Zigzag】
在博客园食用更佳:https://www.cnblogs.com/PinkRabbit/p/AGC017.html。
我们考虑从左到右,依次确定每条折线的形态。
如果直接状压 DP,每次转移一条线,可以做到
哎,你有没有想到,当你做在
一行一行转移,也是复杂度较劣的。那样虽然阶段数是
但是我们有「轮廓线 DP」啊!也就是每次只在一行往前推一格,这样虽然阶段数是
也就做到了
其实这个思想也能用到这题上,如果你也先按照折线从左到右,再按照每条折线从上到下依次确定:
因为上一条折线中,比当前位置往起点的部分其实是不需要了,所以可以不记那些状态(参考轮廓线 DP 时上一层状态)。
此时阶段数为
而转移是
我们需要最后一个优化:把记录上一条折线在此时的高度时的水平坐标这一个信息去掉。
注意如果在此时的高度时,上一条折线比这条折线的水平位置严格靠左,那么这个点其实是毫无意义的。
实际上所有这条折线永远都走不到的地方,都是无意义的。
也就是说上一条折线,太靠左了的话,有一部分信息是没必要的。
那么我们把上一条折线右侧的区域,和这条折线未来可能走到的区域,取个交,也无妨。
这样也就是说上一条折线是直接从当前位置出发的了,而当前位置可以直接由这条折线之前的路径确定。
这样就成功去掉一个
仅有上一条折线往左,而这条折线想往右时,需要把上一条折线多余的部分抹掉,变成从当前点出发的样子。
时间复杂度为
#include <cstdio>
const int Mod = 1000000007;
const int MN = 20, MM = 20;
inline void Add(int &x, int y) { x -= Mod - y; x += x >> 31 & Mod; }
int N, M, K, t[MM][MN];
int f[2][1 << MN];
int main() {
scanf("%d%d%d", &N, &M, &K), --N;
for (int i = 0; i < M; ++i)
for (int j = 0; j < N; ++j)
t[i][j] = -1;
for (int i = 1; i <= K; ++i) {
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
t[--a][--b] = c;
}
int o = 0;
f[o][0] = 1;
for (int i = 0; i < M; ++i) {
for (int j = 0; j < N; ++j) {
o ^= 1;
for (int S = 0; S < 1 << N; ++S) f[o][S] = 0;
for (int S = 0; S < 1 << N; ++S) if (f[o ^ 1][S]) {
int v = f[o ^ 1][S];
if (t[i][j] != 1) {
if (~S >> j & 1) Add(f[o][S], v);
}
if (t[i][j] != 0) {
if (S >> j & 1) Add(f[o][S], v);
else {
int T = S >> j;
if (T) T &= T - 1;
T = (T + 1) << j | (S & ((1 << j) - 1));
Add(f[o][T], v);
}
}
}
}
}
int Ans = 0;
for (int S = 0; S < 1 << N; ++S) Add(Ans, f[o][S]);
printf("%d\n", Ans);
return 0;
}