1 条题解
-
0
自动搬运
来自洛谷,原作者为

strcmp
每一个不曾起舞的日子,都是对生命的辜负搬运于
2025-08-24 21:55:00,当前版本为作者最后更新于2023-03-24 22:13:05,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
题目大意: 给定一个 的字符串网格图,字符集为 $\{\texttt{L},\,\texttt{R},\,\texttt{U},\,\texttt{D}\}$,分别代表若进入当前网格,则需要分别向左,右,上,下方向走一格。求出一个修改网格图中字符的方案,使得从任意初始网格出发,经过若干轮之后都会回到初始网格,输出最小的修改次数。
Solution
一般来说这种状压很难做的小范围的网格题,往网络流的方向想是没有错的。
- 结论 : 若将答案的网格图中网格所指向的方向连一条有向边,建出的有向图满足所有结点的入度都为 。
证明:
显然,所有结点的出度都为 ,图中结点的出度之和为 。
又,有向图中结点入度之和与出度之和相等,所以图中结点入度之和为 。
考虑反证法,设存在一个结点入度大于等于 。则剩下 个结点的入度之和必然小于或等于 ,根据抽屉原理,必然存在一个结点入度为 。而从入度为 的结点出发,必然不存在任何进入该结点的路径,所以没有结点的入度大于等于 。结点入度为 的情况同理。
证毕。
- 结论 :在任意合法方案中,若将任意结点所出发的回路视记录下来,这些回路两两之间必然不相交。
证明:假设存在两个回路相交,则在其中某一回路的某一结点上必然存在出边同时指向该回路的某个结点和另一个回路的某个结点,与结点出度都为 矛盾,所以回路之间必然两两不相交。(这点很重要)
如果您做过 P4003,这道题就是一个弱化版。
由于要同时方案的合法性和修改的最优性,考虑费用流。
为了同时描述每个结点是出流还是入流,我们选择拆点,将 拆为 和 ,分别表示当前的流是要从 出去还是直接从 进去。
每个结点都可以出发,于是建立超级源点 ,从 连向所有结点 的入点 。容量为 ,费用为 。
可以无代价的走到当前方向所代表的位置,可以直接连对应方向的结点的出点,容量为 ,费用为 。其他方向需要一次费用为 的选择,容量仍然为 ,费用为 。(这里可以使得容量为 的原因就是引理 的限制,每一个结点必然只被一个回路经过)
此时我们对每个结点的出点,直接连向超级汇点 即可。
跑费用流,费用流即为答案。
考虑为什么这样做是对的。
首先证明其合法性。
要证明合法性,首先有 时答案的存在性。
这本质上是网格图的多重回路覆盖问题计数,可以直接构造,略。
证明了存在性,则若要使得最大流最大,每个结点必然出去了一个流,进入了一个流,使得总和为 ,按照流的方向构造方案。这点可以直接构造一个多重回路覆盖然后将环上的结点的流强制导向下一个结点,最大流已经是 了,该方案的合法性是显然的。
接下来证明其最优性,假设存在方案使得最小费用不优,则直接按照该方案,在环上将每个结点导向环上下一个结点的出点。该流是合法的,与最小费用矛盾。所以最小费用流的费用即为最优方案的答案。
参考代码:(远古代码,请见谅)
#include <bits/stdc++.h> using namespace std; #define inf 1000000000000000 #define V 100100 #define E 500100 typedef long long int ll; struct edge { int to, next; ll capa, cost; }; int cnt = 0, head[V], n, m; edge node[E]; inline void add(int fir, int nxt, ll w, ll c) { node[cnt].to = nxt, node[cnt].capa = w, node[cnt].cost = c, node[cnt].next = head[fir], head[fir] = cnt++; } int s, t, cur[V]; deque<int>que; ll dep[V], sum = 0, cost = 0; bool vis[V]; inline bool spfa() { for (register int i = 1; i <= t; ++i)dep[i] = inf; dep[s] = 0; que.push_back(s); int u, v; while (!que.empty()) { v = que.front(); que.pop_front(); for (register int i = head[v]; i != -1; i = node[i].next) { u = node[i].to; if (dep[v] + node[i].cost < dep[u] && node[i].capa) { dep[u] = dep[v] + node[i].cost; if (!que.empty() && dep[u] < dep[que.front()])que.push_front(u); else que.push_back(u); } } } return (dep[t] != inf); } ll dfs(register int v, register ll flow) { if (flow == 0 || v == t)return flow; ll used = 0, wei = 0; vis[v] = true; for (register int i = cur[v]; i != -1; i = node[i].next) { cur[v] = i; if (!vis[node[i].to] && dep[node[i].to] == dep[v] + node[i].cost && node[i].capa) { wei = dfs(node[i].to, min(flow - used, node[i].capa)); if (wei) { node[i].capa -= wei, node[i ^ 1].capa += wei, used += wei, cost += node[i].cost * wei; } } if (used == flow)break; } vis[v] = false; return used; } inline void Dinic() { while (spfa()) { memcpy(cur, head, (t + 1) * sizeof(int)); sum += dfs(s, inf); } } inline void addE(int u, int v, ll w, ll c) { add(u, v, w, c); add(v, u, 0, -c); } inline int iid(int x, int y) { return (x - 1) * m + y; } inline int oid(int x, int y) { return iid(x, y) + n * m; } int mtx[205][205]; char c; int dx[5] = { 0, 1, 0 ,-1, 0 }, dy[5] = { 0, 0, 1, 0, -1 }; int main() { ios::sync_with_stdio(0); cin.tie(); cout.tie(); memset(head, -1, V * sizeof(int)); cin >> n >> m; s = 2 * n * m + 1, t = 2 * n * m + 2; for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { cin >> c; if (c == 'D')mtx[i][j] = 1; else if (c == 'R')mtx[i][j] = 2; else if (c == 'U')mtx[i][j] = 3; else mtx[i][j] = 4; } } for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { addE(s, iid(i, j), 1, 0); addE(oid(i, j), t, 1, 0); } } for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { for (int k = 1; k <= 4; k++) { int nx = i + dx[k], ny = j + dy[k]; if (nx == 0)nx = n; else if (nx == n + 1)nx = 1; if (ny == 0)ny = m; else if (ny == m + 1)ny = 1; if (k != mtx[i][j])addE(iid(i, j), oid(nx, ny), 1, 1); else addE(iid(i, j), oid(nx, ny), 1, 0); } } } Dinic(); cout << cost << "\n"; return 0; }
- 1
信息
- ID
- 2904
- 时间
- 1000ms
- 内存
- 125MiB
- 难度
- 6
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者