AGC017F Zigzag【状压 DP】
以下认为
首先,我们可以根据每次走的方向用一个二进制数来表示一条折线。这样显然有一个傻逼 DP,设
注意到这个转移的形式和轮廓线 DP 十分类似,于是我们可以使用同样的方式优化:先按照折线从左到右,再按照每条折线从上到下依次确定,那么上一条折线中比当前点往前的部分都不用考虑了。于是我们可以修改一下 DP 的定义,设
上面做法的瓶颈在于,在抹掉上一条折线的前半部分时我们把它的位置信息也给抹掉了,这时候我们不得不多用一个状态
顺着这个思路我们继续往下想。如果执意要完整记录下上一条折线的话,看起来并没有很好的解决方案。但是仔细想想,我们其实只关心上一条折线限制了当前折线的哪些位置,而对于一些不可能走到的位置,其实可以不记录那些状态。也就是说,我们把上一条折线右侧的区域,和这条折线未来可能走到的区域,取个交,对答案也没有影响。
从官方题解里贺来的图:
于是我们可以认为上一条折线是直接从当前位置出发的,而当前位置可以直接由
我们修改一下 DP 的定义:设
考虑如何转移,我们设上一条折线对当前折线的限制为
-
若
,那么不需要对 进行修改。 -
否则一定有
且 。如果不存在 使得 ,那么我们将 修改为 。否则,找到最小的这样的 ,将 修改为 ,将 修改为 即可。可以结合图片理解这个过程。
上述操作都可以利用二进制操作在
code
/*
挥拂去蒙尘半生的晦暗
用这嶙峋双臂迎接 振翅吧 我的蝴蝶
最后一支 赤诚赞歌盘旋
潮起潮落翻覆昼夜 澎湃在生命刻度之前
此刻色彩挣脱谎言 献予你 赤红纸花遍野
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 20, mod = 1e9 + 7;
int n, m, k, t[N][N], f[2][1 << N];
void add(int &x, int y) { x += y; if (x >= mod) x -= mod; }
int main() {
ios :: sync_with_stdio(0);
cin >> 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, a, b, c; i <= k; i++) {
cin >> 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]);
cout << ans << endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通