P2765 魔术球问题(最小路径覆盖+打印路径+匈牙利)

传送门

题目描述:

假设有 n 根柱子,现要按下述规则在这 nn 根柱子中依次放入编号为 123,...的球“

  1. 每次只能在某根柱子的最上面放球。

  2. 同一根柱子中,任何 2个相邻球的编号之和为完全平方数。

试设计一个算法,计算出在 n 根柱子上最多能放多少个球。例如,在 4 根柱子上最多可放 11 个球。

对于给定的 n,计算在 n 根柱子上最多能放多少个球。

思路:1-n的n个数,让我们去放置在N个格子里面,每两个数之间的和必须是完全平方数,问最大的n可以取到

多少。

我们观察样例N为4的结果,

11
1 8
2 7 9
3 6 10
4 5 11
是不是可以发现,结果是一条条路径,而且每条路径中的点都不相交,也就是n个数的最小路径覆盖!
如何建图呢,我们首先把N*N范围内完全平方数找出来,然后二分判断n是否满足,然后具体判断做法
就是把1-n中和为完全平方数的两个数之间连边,然后再套上最小路径覆盖的模板(我用的匈牙利),
判断最小路径覆盖个数是否是小于等于当前二分出来的n的,是则左边界右移,不是则右边界左移。
最终确定最大的n,然后再跑一遍匈牙利,找出路径,最后再打印即可。
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 600005;
const int inf = 0x3f3f3f3f;
struct edge {
    int f, t, nxt;
}e[maxn * 2];
int hd[maxn], tot = 1;
void add(int f, int t) {
    e[++tot] = { f,t,hd[f] };
    hd[f] = tot;
}
int n;
bool ispow[maxn], vis[maxn];
int match[maxn],nxt[maxn];
bool dfs(int u) {
    for (int i = hd[u]; i; i = e[i].nxt) {
        int v = e[i].t;
        if (vis[v])continue;
        vis[v] = 1;
        if (!match[v] || dfs(match[v])) {
            nxt[u] = v;
            match[v] = u;
            return true;
        }
    }
    return false;
}
bool check(int x) {
    memset(match, 0, sizeof(match));
    memset(nxt, 0, sizeof(nxt));
    memset(hd, 0, sizeof(hd)); tot = 1;
    for (int i = 1; i <= x; i++) {
        for (int j = i + 1; j <= x; j++) {
            if (ispow[i + j]) {
                add(i, j);
                //add(j, i);//不要加回边,不然不能构成DAG,没法匹配
            }
        }
    }
    int sum = 0;
    for (int i = 1; i <= x; i++) {
        memset(vis, 0, sizeof(vis));
        if (dfs(i)) {
            sum++;
        }
    }
    return (x-sum) <= n;
}
int main() {
    //freopen("test.txt", "r", stdin);
    scanf("%d", &n);
    int l = 1, r = n*n;
    for (int i = 1; i <= n*n; i++) {//预处理找出完全平方数
        ispow[i * i] = 1;
    }
    int ans;
    while (l <= r) {
        int mid = (l + r) >> 1;
        if (check(mid)) {
            ans = mid;
            l = mid + 1;
        }
        else{
            r = mid - 1;
        }
    }
    printf("%d\n", ans);//找到ans
    check(ans);//再跑一边
    for (int i = 1; i <= ans; i++) {
        if (!match[i]) {//是起点
            printf("%d", i);
            int p = nxt[i];
            while (p) {
                printf(" %d", p);
                p = nxt[p];
            }
            printf("\n");
        }
    }
    return 0;
}

 

posted @ 2021-04-13 10:06  cono奇犽哒  阅读(57)  评论(0)    收藏  举报