P2765 魔术球问题(最小路径点覆盖)

link

这个题目很不同,它给出的是柱子的数量,要反推球的数量。

可以这样认为,给出边数,求上面的点数。

  • 每次只能在某根柱子的最上面放球 -> 点的连接方式是一串串的,易发现图是个 DAG

然后好像没什么可推的性质了。

题目没给出点数,那肯定要去不断试不同的点数 n ,每次进行判定是否符合条件,这是很朴素的想法。

可以对当前 n 讨论,

因为图建模要保证任意两点编号和为完全平方数,考虑 \(O(n^2)\) 互相枚举所有 1 ~ n 的点,满足条件就连边。

发现图建出来,存在很多条子路径上任意两点都满足题目性质,而要用柱子串住这些点,

不就是 最小路径点覆盖 问题吗?


现在推判定条件,求最多能有多少个点能被 m 条路径覆盖,

  • 当 res < m,显然不够,还要加点;
  • 当 res = m,可能还有更大的答案,继续加点;
  • 当 res = m + 1,这样考虑,这个条件等价于多出刚好一个点,需要刚好多加一条边覆盖,而这个状态上再去掉这个点,就一定是 m 条边能覆盖的最大点数,否则矛盾;

所以当判定符合条件后,n-1 便是答案,注意:因为改变了点,所以之后要再跑一边增广。


然鹅,这样做会 T,

显然 \(O(n^2)\) 的建图不够优,发现题中的图是连续的,后边的点可以在原有图的基础上加。

所以不需要每次推倒重来, \(O(n)\) 连续建图即可。

同样的,因为图是连续的,所以很多答案是之前跑增广就得到的匹配答案,对当前,如果已经匹配有点了,直接更新即可。

#include <bits/stdc++.h>
#define re register int 
using namespace std;
const int N = 2e3;
struct edge
{
	int to, next;
}e[N * N];
int top, h[N];
int match[N], rev_m[N], res;
int m, n;
bool vis[N], used[N];
map<int, bool> f;

inline void add(int x, int y)
{
	e[++ top] = (edge){y, h[x]};
	h[x] = top;
}

bool dfs(int u)
{
	for (re i = h[u]; i; i = e[i].next)
	{
		int v = e[i].to;
		if (vis[v] || v > n) continue;
		vis[v] = true;
		if (!match[v] || dfs(match[v]))
		{
			match[v] = u;
			rev_m[u] = v;
			return true;
		}
	}
	return false;
}

inline int work()
{
	res = 0;
	for (re i = 1; i <= n; i ++)
	{
		memset(vis, false, sizeof(vis));
		res += (rev_m[i] || dfs(i)); // 优化 2 
	}
	
	return n - res;
}

inline void pass(int u)
{
	cout << u << ' ';
	used[u] = true;
	
	if (!rev_m[u] || used[rev_m[u]]) return;
	pass(rev_m[u]);
}

inline void print()
{		
	for (re i = 1; i <= n; i ++)
		if (!used[i])
		{
			pass(i);
			cout << '\n';
		}

}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	
	cin >> m;
	for (re i = 1; i <= N; i ++) f[i * i] = true;
	
	while (++ n)
	{
		for (re i = 1; i < n; i ++)
			if (f[i + n]) add(i, n); // 优化 1 
			
		if (work() == m + 1)
		{
			n --;
			cout << n << '\n';
			break;
		}
	}
	work();
	print();
	
	return 0;
}
posted @ 2024-05-14 22:48  Zhang_Wenjie  阅读(19)  评论(0)    收藏  举报