GDCPC 2024 F Graph Solution
GDCPC 2024 F Graph Solution
题面
见此处。
思路
当时在赛场上直接跳过了这一题。
后来发现只是个大模拟,后悔不已。
由 \(k=\lceil\frac{m}{n-1}\rceil\) 想到构造树来区分路径,加入边时使用二分查找最小的可合并森林编号。
为了这道题十分特殊的空间申请方法,我写了一份符合周礼的代码。
下面是解析。
解析
- 并查集
首先我们需要一个并查集。
class dsu
{
private:
vi f;
int n;
int find(int x)
{
return x == f[x] ? x : f[x] = find(f[x]);
}
public:
dsu(int tn) : n(tn), f(tn + 1)
{
for (int i = 1; i <= tn; i++)
{
f[i] = i;
}
}
bool conn(int x, int y)
{
return find(x) == find(y);
}
void merge(int x, int y)
{
if (conn(x, y))
return;
f[find(y)] = find(x);
}
};
这个并查集提供了一个额外函数 conn(x,y)
,意义为判断并查集中 \(x\) 与 \(y\) 点是否连通。
- 森林
其次我们需要一片森林。
class graph
{
private:
int n;
dsu ds;
vector<vi> road;
vi f;
public:
graph(int tn) : n(tn), ds(tn), road(tn + 1, vi{}), f(tn + 1, 0)
{
}
bool disc(int x, int y)
{
return !ds.conn(x, y);
}
void insert(int x, int y)
{
ds.merge(x, y);
road[x].push_back(y);
road[y].push_back(x);
}
void init(int x)
{
for (auto &i : road[x])
{
if (i == f[x])
continue;
f[i] = x;
init(i);
}
}
void build(int rt)
{
f[rt] = rt;
init(rt);
}
vi extract(int x, int y)
{
vi ret;
while (x != y)
ret.push_back(x), x = f[x];
ret.push_back(y);
return ret;
}
};
该图中提供的 disc(x,y)
函数通过调用并查集中的 conn(x,y)
函数实现,保证该图是一片森林。
extract(x,y)
表示提取树中从 \(x\) 到 \(y\) 的路径,调用条件是 \(y\) 是 \(x\) 的祖先。
- 森林集
接下来我们需要将这些森林组合起来。
class graphset
{
private:
vector<graph> grph;
public:
graphset(int n, int m) : grph((m + n - 2) / (n - 1), n) {}
bool connect(int x, int y)
{
int l = 0, r = grph.size();
while (l < r)
{
int mid = (l + r) >> 1;
if (grph[mid].disc(x, y))
r = mid;
else
l = mid + 1;
}
if (l < grph.size())
grph[l].insert(x, y);
return l >= grph.size() - 1;
}
void print(int x, int y)
{
printf("%d %d\n", x, y);
for (auto &i : grph)
{
i.build(y);
vi tmp = i.extract(x, y);
printf("%d ", tmp.size());
for (auto &j : tmp)
{
printf("%d ", j);
}
putchar('\n');
}
}
};
connect(x,y)
函数会在所有的 \(k=\lceil\frac{m}{n-1}\rceil\) 片森林中选择编号最小的可以进行合并的森林加入边,并在可以构造出答案时返回 true
。
进行上述准备工作后,核心代码的长度会相对较短。
void run()
{
scanf("%d%d", &n, &m);
graphset gph(n, m);
rx = ry = -1;
for (int i = 1, x, y; i <= m; i++)
{
scanf("%d%d", &x, &y);
if (!gph.connect(x, y))
continue;
rx = x, ry = y;
}
if (rx == ry)
{
puts("-1");
return;
}
gph.print(rx, ry);
}
int main()
{
int T = 1;
scanf("%d", &T);
while (T--)
run();
}
于是这道题就做完了。