洛谷 P2765 魔术球问题 题解
作者:岸芷汀兰
一、题目:
二、思路:
既然是网络流24题中的一道,那么肯定要用到网络流模型。
其实大多数OI题都是模型的运用和转化,此题也不例外。
如果没有做过这道题的同学可以先做一下,再来理解本题可能会容易一些。
我们会发现如果我们这样建图:
\(\forall u < v\)如果\(u + v\)是完全平方数,那么我们就从u向v连一条有向边,那么最终的图G一定是个DAG。
每一根柱子,记最下面的数为\(x\),最上面的数为\(y\),那么这根柱子就对应着图G中的一条从\(x\)到\(y\)的路径。
现在把原题改一下,改为给你\(m\),让你求:要想把\(1\sim m\)的数字摆到柱子上,最小需要几个柱子。那是不是就对应着图G的最小路径覆盖问题?
那么现在再来考虑本题,本题是给你\(n\)个柱子,让你求最大的\(m\),那么我们可以依次增加\(m\),检查当前的图G的最小路径覆盖是否小于等于\(n\),如果大于了\(n\),就立即跳出。
至于怎样输出答案,与“最小路径覆盖”那道题的输出方法一模一样,在此不再赘述。
三、代码:
//好看的代码是理解OI题的基础
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
using namespace std;
#define LL long long
#define mem(s, v) memset(s, v, sizeof s)
inline LL read(void) {
LL x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return f * x;
}
const int inf = 1e9 + 5;
const int maxn = 1e5 + 5;
int to[maxn], nxt[maxn], head[30005], tot = 1;
int cur[maxn];
int n, s, t;
int w[maxn];
inline void add(int x, int y, int z) {
to[++tot] = y; nxt[tot] = head[x]; head[x] = tot; w[tot] = z;
to[++tot] = x; nxt[tot] = head[y]; head[y] = tot;
}
inline int id(int x, int y) { return x + y * 10000; }//拆点
inline void AddEdge(int x) {
for (register int i = 1; i < x; ++i) {
int tmp = i + x;
if ((int)sqrt(tmp) * (int)sqrt(tmp) == tmp) {
add(id(i, 0), id(x, 1), 1);
}
}
add(s, id(x, 0), 1); add(id(x, 1), t, 1);
}//每增加一次m,更新一下图G
int q[maxn], l, r, vis[maxn], h[maxn];
inline bool bfs(void) {
for (register int i = 1; i <= t; ++i) h[i] = inf, cur[i] = head[i], vis[i] = 0;
h[s] = 0; l = r = 1; q[r++] = s;
while (r - l) {
int u = q[l++]; vis[u] = 0;
for (register int i = head[u]; i; i = nxt[i]) {
int v = to[i];
if (w[i] && h[v] > h[u] + 1) {
h[v] = h[u] + 1;
if (!vis[v]) q[r++] = v, vis[v] = 1;
}
}
}
return h[t] < inf;
}
int dfs(int u, int flow) {
if (u == t) { return flow; }
int tmp, used = 0;
for (register int i = cur[u]; i; i = nxt[i]) {
int v = to[i]; cur[u] = i;
if (w[i] && h[v] == h[u] + 1) {
if (tmp = dfs(v, min(flow - used, w[i]))) {
w[i] -= tmp; w[i ^ 1] += tmp;
used += tmp;
if (used == flow) break;
}
}
}
return used;
}
int maxflow;
inline void dinic(void) {
while (bfs()) {
maxflow += dfs(s, inf);
}
}
int suc[maxn];
inline void print(int x) {//输出“路径”
while (x) {
printf("%d ", x);
vis[x] = 1;
x = suc[x];
}
}
int main() {
n = read(); s = id(10000, 1) + 1, t = s + 1;
int now = 0, cnt = 0;
while (now <= n) {
++cnt; AddEdge(cnt);
dinic();
now = max(now, cnt - maxflow);
}
printf("%d\n", cnt - 1);
for (register int i = 1; i <= cnt - 1; ++i) {
for (register int j = head[i]; j; j = nxt[j]) {
int v = to[j];
if (v != s && v != t) {
if (w[j ^ 1]) {
suc[i] = v - 10000;
break;
}
}
}
}
mem(vis, 0);
for (register int i = 1; i <= cnt - 1; ++i) {
if (!vis[i]) print(i), puts("");
}
return 0;
}