[题解]神奇操作

\[\color{red}{\text{校长者,真神人也,左马桶,右永神,会执利笔破邪炁,何人当之?}} \\ \begin{array}{|} \hline \color{pink}{\text{The principal is really a god}} \\ \color{pink}{\text{with a closestool on the left and Yongshen on the right}} \\ \color{pink}{\text{holding a sharp pen to pierce the truth}} \\ \color{pink}{\text{Who can resist him? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{green}{\text{校長は本当に神であり、左側にトイレ、右側にヨンシェンがあり}} \\ \color{green}{\text{鋭いペンを持って真実を突き刺している。誰が彼に抵抗できるだろうか? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{lightblue}{\text{Le principal est vraiment un dieu}} \\ \color{lightblue}{\text{avec des toilettes à gauche et Yongshen à droite}} \\ \color{lightblue}{\text{tenant un stylo pointu pour percer la vérité}} \\ \color{lightblue}{\text{Qui peut lui résister ? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{purple}{\text{Der Direktor ist wirklich ein Gott}} \\ \color{purple}{\text{mit einer Toilette links und Yongshen rechts}} \\ \color{purple}{\text{der einen spitzen Stift hält}} \\ \color{purple}{\text{um die Wahrheit zu durchdringen.}} \\ \color{purple}{\text{Wer kann ihm widerstehen? }} \\ \hline \end{array} \\ \begin{array}{|} \hline \color{cyan}{\text{Principalis deus est, Yongshen a dextris cum latrina}} \\ \color{cyan}{\text{acuto stylo ad perforandum veritatem: quis resistet ei? }} \\ \hline \end{array} \\ \color{red}{\text{对曰:“无人,狗欲当之,还请赐教!”}} \\ \newcommand\brak[1]{\left({#1}\right)} \newcommand\Brak[1]{\left\{{#1}\right\}} \newcommand\d[0]{\text{d}} \newcommand\string[2]{\genfrac{\{}{\}}{0pt}{}{#1}{#2}} \newcommand\down[2]{{#1}^{\underline{#2}}} \newcommand\ddiv[2]{\left\lfloor\frac{#1}{#2}\right\rfloor} \newcommand\udiv[2]{\left\lceil\frac{#1}{#2}\right\rceil} \newcommand\lcm[0]{\operatorname{lcm}} \newcommand\set[1]{\left\{{#1}\right\}} \newcommand\ceil[1]{\left\lceil{#1}\right\rceil} \newcommand\floor[1]{\left\lfloor{#1}\right\rfloor} \newcommand\rhs[1]{\;\text{Rhs}\;#1} \newcommand\lhs[1]{\;\text{Lhs}\;#1} \newcommand\Vec[1]{\vec{\mathbf{#1}}} \newcommand\rank[0]{\text{rank}} \]




  \(\mathcal{Back\;To\;The\;Menu}\).

2022-02-26 神奇操作

  玄而又玄的 DP,神而又神的网络流,还有整场考试下来一点思路都没有的玄妙题,所有这一切尽在其中。

高位小游* / Travel

  *:原题目名并不是这个,但是小游又 AK 了😢.

  这个 \(233^{666}\) 十分巨大,实际上就是让我们求出所有疲劳度的 \(f(s)\bmod 2\) 之和。

  考虑枚举每一个维度走出去的长度 \(p_1,p_2,\cdots,p_m\),那么总方案数就是

\[f\brak{\sum_{i=1}^n i\times p_i}=\prod_{i=1}^m{t_0-\sum_{j=1}^{j<i}p_j\choose p_i}\prod_{i=1}^m{t_i\choose p_i}\pmod 2 \]

  不难发现这个函数右边的值都是组合数,考虑组合数在取模下,由卢卡斯定理给出的经典结论:\({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=\bigoplus_{q_i}s\rhs{(q_i-1)} \]

  不难发现,这个 \(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)\).

```cpp /** @author __Elaina__ */

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 inline T fab(T x) { return x < 0 ? -x : x; }
template inline void chkmin(T& x, const T rhs) { x = std::min(x, rhs); }
template inline void chkmax(T& x, const T rhs) { x = std::max(x, rhs); }
template 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 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 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 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 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

&emsp;&emsp;前几组都可以用插头 DP 过掉,这个就不说了。

&emsp;&emsp;接下来是判断无解的情况:实际上就是要选出一些环使得所有的目标点都被覆盖。也就是说,最终的状态是我们要选出一些边使得每个点的度数都为 $2$. 考虑将图**黑白染色**,那么我们就是要在黑点白点之间选边满足前面那个条件,建图跑最大流即可。

&emsp;&emsp;既然都有网络流的铺垫,那么正解也应当是这个方向了。还是考虑先将图黑白染色,一个拐角实际上是一个节点同时拥有一个竖向的度和一个横向的度,因此我们可以将一个点分成竖向和横向两个点,竖直相邻点的竖点之间连边,横向相邻点的横度点之间之间连边,都是不带权的,但是显然不可能每个点都这么好,我们还要考虑一个点不能作为拐角的时候怎么办?其实就是同时拥有两个横度或者两个出度,因此我们再分别向竖点和横点连一条重边,容量依然为 $1$,但是花费为 $w_{ij}$,意为这个点的某一方向无法流时,此时相当于拥有另外一个方向的两个度,此时它无法得到 $w_{ij}$ 的价值。对这个图跑最小费用最大流,用 $\sum w_{ij}$ 减去最小费用即可得到最大价值。

&emsp;&emsp;还是放个图在下面吧?

<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;
}
posted @ 2022-03-14 22:08  Arextre  阅读(21)  评论(0编辑  收藏  举报