这个题目很不同,它给出的是柱子的数量,要反推球的数量。
可以这样认为,给出边数,求上面的点数。
- 每次只能在某根柱子的最上面放球 -> 点的连接方式是一串串的,易发现图是个 DAG;
然后好像没什么可推的性质了。
题目没给出点数,那肯定要去不断试不同的点数 n ,每次进行判定是否符合条件,这是很朴素的想法。
可以对当前 n 讨论,
因为图建模要保证任意两点编号和为完全平方数,考虑 \(O(n^2)\) 互相枚举所有 1 ~ n 的点,满足条件就连边。
发现图建出来,存在很多条子路径上任意两点都满足题目性质,而要用柱子串住这些点,
不就是 最小路径点覆盖 问题吗?
现在推判定条件,求最多能有多少个点能被 m 条路径覆盖,
- 当 res < m,显然不够,还要加点;
- 当 res = m,可能还有更大的答案,继续加点;
- 当 res = m + 1,这样考虑,这个条件等价于多出刚好一个点,需要刚好多加一条边覆盖,而这个状态上再去掉这个点,就一定是 m 条边能覆盖的最大点数,否则矛盾;
所以当判定符合条件后,n -- 便是答案,注意:因为改变了点,所以之后要再跑一边增广。
然鹅,这样做会 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;
}
标签:re,int,res,路径,魔术,点数,match,P2765
From: https://www.cnblogs.com/hi-zwjoey/p/18192443