[题解]神奇操作
\(\mathcal{Back\;To\;The\;Menu}\).
2022-02-26 神奇操作
玄而又玄的 DP,神而又神的网络流,还有整场考试下来一点思路都没有的玄妙题,所有这一切尽在其中。
高位小游* / Travel
*:原题目名并不是这个,但是小游又 AK 了😢.
这个 \(233^{666}\) 十分巨大,实际上就是让我们求出所有疲劳度的 \(f(s)\bmod 2\) 之和。
考虑枚举每一个维度走出去的长度 \(p_1,p_2,\cdots,p_m\),那么总方案数就是
不难发现这个函数右边的值都是组合数,考虑组合数在取模下,由卢卡斯定理给出的经典结论:\({n\choose m}\pmod 2\) 只有当 \(n\) 在二进制下为 \(m\) 的超集时,其值为 \(1\). 不妨考察右边为 \(1\) 的充要条件:\(p_i\) 为 \(t_i\) 子集且 \(\set{p_1,p_2\cdots,p_m,t_0-\sum{p_i}}\) 是 \(t_0\) 的二进制划分。
关于后者,可能有人会想到 \(t_0-\sum p_j\) 二进制下退位了怎么办?—— 退位了就证明前面某个 \(p_i\) 的某个 \(1\) 出现在 \(t_0-\sum ^{j< i}_{j=1}p_j\) 没有 \(1\) 的地方了,此时已经为 \(0\) 了。
然后就到了玄之又玄的转化:既然我们要求的是 \(p_i\subset t_i\) 并且 \(\set{p_1,p_2\cdots,p_m,t_0-\sum{p_i}}\) 是 \(t_0\) 的二进制划分,那么我们把它摆到矩阵上去:现有一个 \(31\times (m+1)\) 的矩阵 \(A\),\(a_{ij}=1\) 当且仅当 \(j=1\)(相当于 \(t_0\) 这一位被分到 \(t_0-\sum p_i\) 里面去了)或者 \(t_0,t_{j-1}\) 的第 \(i-1\) 位同时为 \(1\). 现在要从每一行中选一个 \(1\),假设第 \(i\) 行选择的第 \(q_i\) 个位置,那么对于一个 \(\set{q_i}\),其疲劳度为 \(\sum_{i=1}^{31}2^{i-1}(q_i-1)\). 求 \(\sum \brak{f(s)\bmod 2}\) 的值.
我们可以用一个背包,\(dp(i,t)\) 表示到了第 \(i\) 行,疲劳度为 \(t\) 的方案数对二取模的结果,转移就是将在第二维加上 \(2^{i-1}\times (q_i-1)\) 的位置 \(dp(i+1,t+2^i\times (q_i-1))\) 异或上 \(dp(i,t)\) 即可,显然疲劳度的范围是 \(\mathcal O(mt_0)\) 级别,因此该算法复杂度为 \(\mathcal O(mt_0\times \log^2 t_0)\).
考虑优化这个过程,由于我们每次加上的数是 \(2^i\cdot v\),我们不妨考虑按照对 \(2^i\) 取模为相同余数的位置分组,显然会有 \(2^i\) 组,它是剩余系的大小,但是每一组只会有至多 \(m\) 个元素,因为当前的疲劳值最多 \(m\times 2^i\). 这样分组有什么好处?由于我们每次加上的是 \(2^i\cdot v\),因此,不同的组之间是不会相互干扰的,而且,我们可以将每一组的 \(m\) 个位置的 \(0/1\) 状压进 \(2^m\) 个状态中,显然,压缩状态相同的不同组转移是相同且互不干扰的,因此我们就可以进行批量转移,所以可以设一个新的状态:\(f(i,s)\) 表示到第 \(i\) 行,压缩状态为 \(s\) 的有多少组,至于转移,我们枚举可行的 \(q_i\),那么下一层的状态应当是
不难发现,这个 \(nxt\) 应当是一个长度为 \(2m\) 的串,而这个应当是 \(i+1\) 层的目标状态,但是第 \(i+1\) 层的疲劳值模数从 \(2^i\) 变成了 \(2^{i+1}\),它会导致分组方式发生变化,但是,不难发现,实际上下一层的两种状态应当是分别将 \(nxt\) 的奇数位和偶数位拆出来之后重新组合而成。因此我们也可以找到 \(i+1\) 的目标状态了,将 \(f(i,s)\) 加上去即可。总时间复杂度 \(\mathcal O(m2^{m+1}\log t_0)\).
pragma GCC optimize(3)
include <bits/stdc++.h>
using namespace std;
define USING_FREAD
// #define NDEBUG
include
namespace Elaina {
/** その可憐な少女は魔女であり、旅人でした。 ―― そう、私です! */
define rep(i, l, r) for(int i = (l), i##end = (r); i <= i##end; ++i)
define drep(i, l, r) for(int i = (l), i##end = (r); i >= i##end; --i)
define fi first
define se second
define mp(a, b) make_pair(a, b)
define Endl putchar('\n')
define whole(v) ((v).begin()), ((v).end())
define bitcnt(s) (__builtin_popcount(s))
/** @warning no forced type conversion */
define rqr(x) ((x) * (x))
define y0 FUCK_UP
define y1 MOTHER_FUCKER
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> pii;
template
template
template
template
ifdef USING_FREAD
inline char qkgetc() {
define BUFFERSIZE 1 << 20
static char BUF[BUFFERSIZE], *p1 = BUF, *p2 = BUF;
return p1 == p2 && (p2 = (p1 = BUF) + fread(BUF, 1, BUFFERSIZE, stdin), p1 == p2) ? EOF : *p1++;
undef BUFFERSIZE
}
define CHARRECEI qkgetc()
else
define CHARRECEI getchar()
endif
template
x = 0; int f = 0; char c;
while (!isdigit(c = CHARRECEI)) if(c == '-') f = 1;
for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
return f ? -x : x;
}
template
x = 0; int f = 0; char c;
while (!isdigit(c = CHARRECEI)) if (c == '-') f = 1;
for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
if (f) x = -x;
}
template<class T, class... Args> inline void readin(T& x, Args&... args) {
readin(x), readin(args...);
}
template
if (x < 0) putchar('-'), x = -x;
static int __stk[55], __bit = 0;
do __stk[++__bit] = x % 10, x /= 10; while (x);
while (__bit) putchar(__stk[__bit--] ^ 48);
putchar(c);
}
} // namespace Elaina
using namespace Elaina;
const int Maxm = 10;
int T, m;
int a[Maxm + 5];
ll f[35][(1 << Maxm + 1) + 5];
inline void solve() {
readin(m);
rep (i, 0, m) readin(a[i]);
memset(f, 0, sizeof f);
f[0][1] = 1;
int U = 1 << m + 1;
rep (i, 0, 30) {
int x = 0;
rep (j, 0, m) if (a[j] >> i & 1)
x ^= 1 << j;
if (x & 1 ^ 1) x = 1; // a[0] is not equal to 1 at position i
for (int s = 0; s < U; ++s) {
int nxt = 0, to[2] = {};
rep (j, 0, m) if (x >> j & 1)
nxt ^= s << j;
drep (j, m << 1, 0) to[j & 1] = to[j & 1] << 1 | (nxt >> j & 1);
f[i + 1][to[0]] += f[i][s], f[i + 1][to[1]] += f[i][s];
}
}
ll ans = 0;
for (int s = 0; s < U; ++s)
ans += f[31][s] * (ll)(__builtin_popcount(s));
writln(ans);
}
signed main() {
freopen("travel.in", "r", stdin);
freopen("travel.out", "w", stdout);
rep (_, 1, readret(1)) solve();
return 0;
}
</details>
### 过山车 / Roller
  前几组都可以用插头 DP 过掉,这个就不说了。
  接下来是判断无解的情况:实际上就是要选出一些环使得所有的目标点都被覆盖。也就是说,最终的状态是我们要选出一些边使得每个点的度数都为 $2$. 考虑将图**黑白染色**,那么我们就是要在黑点白点之间选边满足前面那个条件,建图跑最大流即可。
  既然都有网络流的铺垫,那么正解也应当是这个方向了。还是考虑先将图黑白染色,一个拐角实际上是一个节点同时拥有一个竖向的度和一个横向的度,因此我们可以将一个点分成竖向和横向两个点,竖直相邻点的竖点之间连边,横向相邻点的横度点之间之间连边,都是不带权的,但是显然不可能每个点都这么好,我们还要考虑一个点不能作为拐角的时候怎么办?其实就是同时拥有两个横度或者两个出度,因此我们再分别向竖点和横点连一条重边,容量依然为 $1$,但是花费为 $w_{ij}$,意为这个点的某一方向无法流时,此时相当于拥有另外一个方向的两个度,此时它无法得到 $w_{ij}$ 的价值。对这个图跑最小费用最大流,用 $\sum w_{ij}$ 减去最小费用即可得到最大价值。
  还是放个图在下面吧?
<center>
<img src="https://s4.ax1x.com/2022/03/01/b1lyWj.png">
</center>
<details>
```cpp
/** @author __Elaina__ */
#include <bits/stdc++.h>
using namespace std;
#define USING_FREAD
// #define NDEBUG
#include <cassert>
namespace Elaina {
/** その可憐な少女は魔女であり、旅人でした。 ―― そう、私です! */
#define rep(i, l, r) for(int i = (l), i##_end_ = (r); i <= i##_end_; ++i)
#define drep(i, l, r) for(int i = (l), i##_end_ = (r); i >= i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define whole(v) ((v).begin()), ((v).end())
#define bitcnt(s) (__builtin_popcount(s))
/** @warning no forced type conversion */
#define rqr(x) ((x) * (x))
#define y0 FUCK_UP
#define y1 MOTHER_FUCKER
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> pii;
template<class T> inline T fab(T x) { return x < 0 ? -x : x; }
template<class T> inline void chkmin(T& x, const T rhs) { x = std::min(x, rhs); }
template<class T> inline void chkmax(T& x, const T rhs) { x = std::max(x, rhs); }
template<class T> inline void myswap(T& x, T& y) { x ^= y ^= x ^= y; }
#ifdef USING_FREAD
inline char qkgetc() {
# define BUFFERSIZE 1 << 20
static char BUF[BUFFERSIZE], *p1 = BUF, *p2 = BUF;
return p1 == p2 && (p2 = (p1 = BUF) + fread(BUF, 1, BUFFERSIZE, stdin), p1 == p2) ? EOF : *p1++;
# undef BUFFERSIZE
}
# define CHARRECEI qkgetc()
#else
# define CHARRECEI getchar()
#endif
template<class T> inline T readret(T x) {
x = 0; int f = 0; char c;
while (!isdigit(c = CHARRECEI)) if(c == '-') f = 1;
for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
return f ? -x : x;
}
template<class T> inline void readin(T& x) {
x = 0; int f = 0; char c;
while (!isdigit(c = CHARRECEI)) if (c == '-') f = 1;
for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
if (f) x = -x;
}
template<class T, class... Args> inline void readin(T& x, Args&... args) {
readin(x), readin(args...);
}
template<class T> inline void writln(T x, char c = '\n') {
if (x < 0) putchar('-'), x = -x;
static int __stk[55], __bit = 0;
do __stk[++__bit] = x % 10, x /= 10; while (x);
while (__bit) putchar(__stk[__bit--] ^ 48);
putchar(c);
}
} // namespace Elaina
using namespace Elaina;
const int Maxn = 150;
const int Maxm = 30;
const int Maxcnt = Maxn * Maxm * 4 + 2;
const int Maxe = Maxcnt * 20;
const int inf = 0x7fffffff;
struct edge { int to, nxt, flow, w; } e[Maxe * 2 + 5];
inline void update(int i, int f) { e[i].flow -= f, e[i ^ 1].flow += f; }
int tail[Maxcnt + 5], ecnt;
inline void add_edge(int u, int v, int f, int w) {
// fprintf(stderr, "add_edge :> u == %d, v == %d, f == %d, w == %d\n", u, v, f, w);
e[ecnt] = edge{ v, tail[u], f, w }; tail[u] = ecnt++;
e[ecnt] = edge{ u, tail[v], 0, -w }; tail[v] = ecnt++;
}
int n, m, ncnt, S, T, sum, cnt;
int w[Maxn + 5][Maxm + 5], fr[Maxn + 5][Maxm + 5];
int r[Maxn + 5][Maxm + 5], c[Maxn + 5][Maxm + 5], p[Maxn + 5][Maxm + 5];
inline void buildGraph() {
memset(tail, 0xff, sizeof tail);
S = ++ncnt, T = ++ncnt;
rep (i, 1, n) rep (j, 1, m) {// distribute id first
p[i][j] = ++ncnt, r[i][j] = ++ncnt, c[i][j] = ++ncnt;
sum += w[i][j] * fr[i][j], cnt += fr[i][j];
}
auto inside = [](int x, int y) {
return 0 < x && x <= n && 0 < y && y <= m;
};
const int dis[][2] = { { 1, 0 }, {-1, 0}, {0, 1}, {0, -1} };
rep (i, 1, n) rep (j, 1, m) if (fr[i][j]) {
if (i + j & 1) { // black block
add_edge(S, p[i][j], 2, 0);
add_edge(p[i][j], r[i][j], 1, 0);
add_edge(p[i][j], r[i][j], 1, w[i][j]);
add_edge(p[i][j], c[i][j], 1, 0);
add_edge(p[i][j], c[i][j], 1, w[i][j]);
} else {
add_edge(p[i][j], T, 2, 0);
add_edge(r[i][j], p[i][j], 1, 0);
add_edge(r[i][j], p[i][j], 1, w[i][j]);
add_edge(c[i][j], p[i][j], 1, 0);
add_edge(c[i][j], p[i][j], 1, w[i][j]);
}
if (i + j & 1 ^ 1) continue; // white block
rep (_, 0, 3) {
int tx = i + dis[_][0], ty = j + dis[_][1];
if (!inside(tx, ty) || !fr[i][j]) continue;
if (_ >> 1 & 1) // the same row
add_edge(r[i][j], r[tx][ty], 1, 0);
else add_edge(c[i][j], c[tx][ty], 1, 0);
}
}
}
queue<int> Q;
int dis[Maxcnt + 5];
bool inq[Maxcnt + 5];
inline bool spfa() {
memset(dis + 1, 0x3f, ncnt << 2);
memset(inq + 1, false, ncnt);
dis[S] = 0, inq[S] = true; Q.push(S);
while (!Q.empty()) {
int u = Q.front(); Q.pop(), inq[u] = false;
// fprintf(stderr, "u == %d, d == %d\n", u, dis[u]);
for (int i = tail[u], v, w; ~i; i = e[i].nxt) if (e[i].flow) {
v = e[i].to, w = e[i].w;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
if (!inq[v]) Q.push(v), inq[v] = true;
}
}
}
return dis[T] < 0x3f3f3f3f;
}
int cur[Maxcnt + 5];
bool vis[Maxcnt + 5];
int dfs(int u, int f) {
// printf("dfs :> u == %d, f == %d\n", u, f);
if (u == T) return f;
vis[u] = true;
int used = 0, v;
for (int& i = cur[u]; ~i; i = e[i].nxt)
if (e[i].flow && dis[v = e[i].to] == dis[u] + e[i].w && !vis[v]) {
int ret = dfs(v, min(f - used, e[i].flow));
used += ret, update(i, ret);
if (used == f) break;
}
vis[u] = false;
return used;
}
int mxflow, mncost;
inline void dinic() {
while (spfa()) {
// fprintf(stderr, "dis == %d\n", dis[T]);
memcpy(cur + 1, tail + 1, ncnt << 2);
int ret = dfs(S, inf);
mxflow += ret, mncost += ret * dis[T];
}
return ;
}
signed main() {
freopen("roller.in", "r", stdin);
freopen("roller.out", "w", stdout);
readin(n, m);
rep (i, 1, n) rep (j, 1, m) readin(fr[i][j]), fr[i][j] = !fr[i][j]; // flip
rep (i, 1, n) rep (j, 1, m) readin(w[i][j]);
buildGraph();
dinic();
if (mxflow != cnt) return writln(-1), 0;
writln(sum - mncost);
return 0;
}