魔术球问题
题面
[魔术球问题](https://www.luogu.com.cn/problem/P2765)
题解
做法有两种,找规律和网络流。
这里提供网络流的做法。
对于一个数 \(i\) 我们向能与它构成完全平方数的数 \(j\) 连边,那么如果我们对它进行二分图匹配的话,匹配边是不是就相当于是 \(i\) 在 \(j\) 的下面?
但是 \(j\) 很多,我们又该枚举到哪里呢?
很简单,因为球是由小到大放入的,所以我们直接枚举球的放入,在当前球放入之前,柱子上的球编号都比当前的球小,那么它们的和最大是小于二倍当前球的编号的,最小是大于当前球的编号的。
所以我们直接枚举 \(i ^ 2\),\(i ^ 2\) 的范围即为上述,然后连 \(i ^ 2 - now\) 和 \(now\) 就好了,一边2枚举一边加边。
接下来是柱子的问题。
我们套路一下,把每个编号拆成两个点,然后一个点连源点,一个点连汇点,因为选择一条 \(i\) 到 \(j\) 的路径表示把 \(j\) 放在 \(i\) 的上面,那么多条这样的路径连起来是不是就是一串小球,是不是就可以抽象为一根柱子。而我们为了保证小球数最多,那么我们要让这些路径的包含的点最多,那么我们就可以对它做最小路径覆盖,因为这样是可以保证在当前已加入的球中每个球都放到了柱子上,且占用的柱子最少。
代码
#include<cstdio>
#include<queue>
#include<cstring>
#include<cmath>
const int N = 1e6 + 5, inf = 0x3f3f3f3f;
int head[N], nex[N << 1], to[N << 1], w[N << 1], tot = 1, S, T, n, front[N]; bool vis[N];
inline void add(int u, int v, int k) {
nex[++tot] = head[u]; to[tot] = v; w[tot] = k; head[u] = tot;
nex[++tot] = head[v]; to[tot] = u; w[tot] = 0; head[v] = tot;
}
namespace Dinic {
int dep[N], now[N], back[N];
std::queue < int > q;
inline int min(int x, int y) { return x < y ? x : y; }
inline bool bfs() {
memset(dep, 0, sizeof(dep));
while(!q.empty()) q.pop();
q.push(S); dep[S] = 1; now[S] = head[S];
while(!q.empty()) {
int x = q.front(); q.pop();
for(int i = head[x], y; i; i = nex[i]) {
if(w[i] && !dep[y = to[i]]) {
q.push(y);
now[y] = head[y];
dep[y] = dep[x] + 1;
if(y == T) return true;
}
}
}
return false;
}
int dinic(int x, int flow) {
if(x == T) return flow;
int rest = flow, k, i;
for(i = now[x]; i && rest; i = nex[i])
if(w[i] && dep[to[i]] == dep[x] + 1) {
k = dinic(to[i], min(w[i], rest));
if(!k) dep[to[i]] = 0;
w[i] -= k;
w[i ^ 1] += k;
back[x >> 1] = to[i] >> 1;
rest -= k;
}
now[x] = i;
return flow - rest;
}
inline int solve() {
int maxflow = 0, flow = 0;
while(bfs())
while((flow = dinic(S, inf))) maxflow += flow;
return maxflow;
}
}
using namespace Dinic;
int main() {
scanf("%d", &n); int now = 0, num = 0;
S = 0, T = 1e6 + 1;
while(num <= n) {
++now;
add(S, now << 1, 1); add(now << 1 | 1, T, 1);
for(int i = sqrt(now) + 1; i * i < (now << 1); i++)
add((i * i - now) << 1, now << 1 | 1, 1);
int Flow = solve();
if(!Flow) front[++num] = now;
}
printf("%d\n", now - 1);
for(int i = 1; i <= n; i++)
if(!vis[front[i]]) {
for(int x = front[i]; x && x != T >> 1; x = back[x])
vis[x] = true, printf("%d ", x);
puts("");
}
return 0;
}

浙公网安备 33010602011771号