【网络流24题】 4. 魔术球问题 题解

题目链接(洛谷 P2765)

题意

假设有\(n\)根柱子,现要按下述规则在这\(n\)根柱子中依次放入编号为\(1, 2, 3, \cdots\)的球:

  • 每次只能在某根柱子的最上面放球。
  • 同一根柱子中,任何\(2\)个相邻球的编号之和为完全平方数。

试设计一个算法,计算出在\(n\)根柱子上最多能放多少个球。例如,在\(4\)根柱子上最多可放\(11\)个球。对于给定的\(n\),计算在\(n\)根柱子上最多能放多少个球。

思路

贪心

对于这道题,我很自然地想到了一个贪心策略。首先,很容易发现,这道题柱子的编号其实根本不重要。那么,我们规定,相同条件下放球时,优先放入编号小的柱子。

当我们放入第\(k\)个球时,我们采用如下贪心策略:

  • \(1\)\(n\)扫描所有柱子。如果\(k\)与某个柱子顶端的球数字之和为完全平方数,则放入这个柱子;
  • 若所有放入过球的柱子都不能放入,则找到下一个没有放入球的柱子放进去;
  • 否则无法放入这个球,输出结果,退出。

正确性

若一个球既可以放入某个有球的柱子,也可以放入某个无球的柱子,那么,放入有球的柱子一定更优。因为将\(k\)放入有球的柱子,会出现一个顶端为\(k\)的柱子和一个空柱子(就是没放进去的那个),而放入空柱子则会出现一个顶端为\(k\)的柱子和一个能与\(k\)匹配的柱子,显然前者比后者更优。

不会出现一个数放入两个已经有球的柱子的情况。因为投入球的顺序一定是从\(1\)\(n\)逐个递增的,按照上述投入球的规则,最大的若干个球一定都在各个最外侧,而且它们的数一定是连续的。(可以按照上述规则手推一下\(n = 4\)的情况)

网络流

其实个人感觉归为网络流不是很合适,因为把本题抽象成图的做法,本质上是做二分图匹配。关于图的点数的通项,并不是我自己想出来的,也无法给出详细的证明,所以建议优先参考贪心做法。

给出结论:若一共有\(n\)个柱子,则总共能放入\(\left\lfloor \dfrac{n \cdot (n + 2) + (n \bmod 2) - 2}{2} \right\rfloor\)个球。

然后,我们很容易想到,我们尽可能把临近的能组成完全平方数的数穿起来,比如\(1 \rightarrow 3 \rightarrow 6\),那么,一个这样的串上的数就能都放到一个柱子上。同时,我们希望这样的串尽可能少。

有没有觉得很熟悉?希望串尽可能少,剩下的部分就和第三题最小路径覆盖问题一样了。

然后,我们能推算出一共有多少球,这样,我们就能构图了,剩下的就同第三题——最小路径覆盖问题一样了。

代码

贪心

/**
 * luogu P2765 https://www.luogu.com.cn/problem/P2765
 * 贪心
 **/

#include <cstdio>
#include <cstring>
#include <vector>
#include <cmath>
#include <algorithm>

using namespace std;

const int maxn = 10000;
vector<int> s[maxn];
int n, ans, d;

int check(const int &x) {	// 判断球x能否放入,能放入则返回放入的柱子编号,否则返回0
    for (int i = 1; i <= n; i++) {
        if (s[i].empty()) return i;
        int t = (int)sqrt(s[i].back() + x);
        if (t * t == (s[i].back() + x))
            return i;
    }
    return 0;
}

int main() {
    scanf("%d", &n);
    while ((d = check(ans + 1))) {
        if (d == 0) break;
        ans++;
        s[d].push_back(ans);
    }
    printf("%d\n", ans);
    for (int i = 1; i <= n; i++) {
        for (auto j : s[i])
            printf("%d ", j);
        putchar('\n');
    }
    return 0;
}

网络流

/**
 * luogu P2765 https://www.luogu.com.cn/problem/P2765
 * 网络流
 **/

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;
const int maxn = 5000;
const int maxm = 1e5 + 5;
const int INF = 0x3f3f3f3f;
const int S = 0;
const int T = maxn - 1;

struct Edge {
    int to, nxt, val;
}e[maxm];

int numedge, head[maxn], n, pre[maxn], nxt[maxn], depth[maxn];
bool ispfs[maxn];

inline void AddEdge(int from, int to, int val) {
    e[numedge].to = to;
    e[numedge].val = val;
    e[numedge].nxt = head[from];
    head[from] = numedge;
    numedge++;
}

inline bool bfs() {
    memset(depth, 0, sizeof(depth));
    depth[S] = 1;
    queue<int> q;
    q.push(S);
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        for (int i = head[u]; ~i; i = e[i].nxt) {
            int to = e[i].to;
            if (!depth[to] && e[i].val > 0) {
                depth[to] = depth[u] + 1;
                q.push(to);
            }
        }
    }
    return depth[T] != 0;
}

inline int dfs(int u, int flow) {
    if (u == T) return flow;
    for (int i = head[u]; ~i; i = e[i].nxt) {
        int to = e[i].to;
        if (depth[to] > depth[u] && e[i].val > 0) {
            int di = dfs(to, min(flow, e[i].val));
            if (di > 0) {
                if (to > n) {
                    pre[to - n] = u;
                    nxt[u] = to - n;
                }
                e[i].val -= di;
                e[i ^ 1].val += di;
                return di;
            }
        }
    }
    return 0;
}

int Dinic() {
    int res = 0;
    while (bfs()) {
        int d = 0;
        while ((d = dfs(S, INF))) {
            res += d;
        }
    }
    return res;
}

int main() {
    memset(head, -1, sizeof(head));
    for (int i = 1; i * i < maxn; i++)
        ispfs[i * i] = true;    // 用来判断i是否是完全平方数

    scanf("%d", &n);
    n = (n * (n + 2) + (n & 1) - 2) / 2;    // 很迷的通项公式
    for (int i = 1; i <= n; i++) pre[i] = nxt[i] = i;

    for (int i = 1; i <= n; i++) {
        for (int j = i + 1; j <= n; j++) {
            if (ispfs[i + j]) {
                AddEdge(i, j + n, 1);
                AddEdge(j + n, i, 0);
            }
        }
    }

    for (int i = 1; i <= n; i++) {
        AddEdge(S, i, 1);
        AddEdge(i, S, 0);
        AddEdge(i + n, T, 1);
        AddEdge(T, i + n, 0);
    }
    Dinic();
    printf("%d\n", n);
    for (int i = 1; i <= n; i++) {
        if (pre[i] == i) {
            int u = i;
            for (u = i; nxt[u] != u; u = nxt[u])
                printf("%d ", u);
            printf("%d\n", u);
        }
    }
    return 0;
}
posted @ 2020-09-03 22:13  icysky  阅读(224)  评论(0编辑  收藏  举报