魔术球问题

题面

[魔术球问题](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;
}
posted @ 2021-07-18 21:42  init-神眷の樱花  阅读(67)  评论(0)    收藏  举报