P2765 魔术球问题(最小路径覆盖+打印路径+匈牙利)
题目描述:
假设有 n 根柱子,现要按下述规则在这 nn 根柱子中依次放入编号为 1,2,3,...的球“
-
每次只能在某根柱子的最上面放球。
-
同一根柱子中,任何 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; }