「解题报告」P9167 [省选联考 2023] 城市建造
考场降智系列。
首先手模一下,发现题意就是让我们选出一个点集,删去之间的所有边,使得这个点集中的每个点都不在同一个连通块中。首先可以发现选择的点集一定是若干个点双,如果一个点双中的点没有全部被选择,那么一定有其中至少两个点仍然在同一连通块中。其次,我们发现选取的点集一定是联通的,否则同样会有两个点在同一连通块中。不难发现这就是点集合法的充要条件。
那么问题转换成了:给定一个图,删去若干个联通的点双,使得剩下的连通块的大小的极差
首先点双容易想到圆方树转成树,现在就是一个树形 DP 了。极差不好做,我们考虑直接枚举连通块的大小。容易发现,当
假如我们枚举当前的连通块大小为
对于选取的连通块,我们在深度最小的位置统计答案,那么在每个点统计它作为选取的连通块的深度最小的点时的方案数即可。这样的复杂度为
以上是考场上想到的做法,但是考场降智,忘了怎么建圆方树了,痛失 40 分。
我们可以更仔细的分析一下这个背包的选取过程。先考虑
据说这个做法卡卡常可以过,但是我自己卡了一上午,仍然只能做到最大的点本机跑 0.8s,luogu 上 TLE。我还是比较菜。
上述做法感觉上其实很多节点都不存在合法的划分方案。我们考虑不枚举连通块大小分别跑 DP,而是对每个连通块大小一起跑 DP。分析上述算法过程,我们发现,每次选取的子树都是一个后缀(中间可能删去一个树)。那么我们可以先枚举选取的这个后缀,然后计算其对应的连通块大小,只对这些连通块大小进行 DP。由于前缀的个数是
视实现而定,可做到
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 200005, P = 998244353;
int n, m, k;
int ans[MAXN][2];
set<int> s;
unordered_map<int, int> f[MAXN][2], fsuf[MAXN];
struct Tree {
vector<int> e[MAXN], re[MAXN];
int tot;
void add(int u, int v) {
e[u].push_back(v), e[v].push_back(u);
}
int siz[MAXN];
int to[MAXN];
void dfs(int u, int pre) {
siz[u] = u <= n;
for (int v : e[u]) if (v != pre) {
dfs(v, u);
siz[u] += siz[v];
}
sort(e[u].begin(), e[u].end(), [&](int a, int b) { return siz[a] < siz[b]; });
int deg = 0;
for (int v : e[u]) if (v != pre) {
deg++;
to[deg] = v;
}
if (u <= n) {
{ // k = 0
for (int i = 1; i <= deg; i++)
fsuf[i].clear();
for (int i = deg; i >= 1; i--) {
int v = to[i];
if (i == deg) fsuf[i] = f[v][0];
else for (auto p : f[v][0]) {
int t = p.first, w = p.second;
fsuf[i][t] = 1ll * fsuf[i + 1][t] * w % P;
}
}
int sz = 1, up = n - siz[u];
for (int i = 1; i <= deg; i++) {
if (siz[to[i]] >= sz) {
if (s.count(sz)) f[u][0][sz] = (f[u][0][sz] + fsuf[i][sz]) % P;
if (s.count(sz + up)) ans[sz + up][0] = (ans[sz + up][0] + fsuf[i][sz + up]) % P;
}
sz += siz[to[i]];
}
if (s.count(sz)) f[u][0][sz] = (f[u][0][sz] + 1) % P;
if (s.count(sz + up)) ans[sz + up][0] = (ans[sz + up][0] + 1) % P;
}
{ // k = 1
for (int i = 1; i <= deg; i++)
fsuf[i].clear();
for (int i = deg; i >= 1; i--) {
int v = to[i];
if (i == deg) fsuf[i] = f[v][1];
else for (auto p : f[v][1]) {
int t = p.first, w = p.second;
fsuf[i][t] = 1ll * fsuf[i + 1][t] * w % P;
}
}
int sz = 1, up = n - siz[u];
for (int i = 1; i <= deg; i++) {
if (siz[to[i]] >= sz) {
if (s.count(sz)) f[u][1][sz] = (f[u][1][sz] + fsuf[i][sz]) % P;
}
if (siz[to[i]] >= sz + up) {
if (s.count(sz + up)) {
ans[sz + up][1] = (ans[sz + up][1] + fsuf[i][sz + up]) % P;
}
}
if (sz - 1 > 0 && siz[to[i - 1]] < sz - 1 && siz[to[i]] >= sz - 1) {
if (s.count(sz - 1)) f[u][1][sz - 1] = (f[u][1][sz - 1] + fsuf[i][sz - 1]) % P;
}
if (sz + up - 1 > 0 && siz[to[i - 1]] < sz + up - 1 && siz[to[i]] >= sz + up - 1) {
if (s.count(sz + up - 1)) {
ans[sz + up - 1][1] = (ans[sz + up - 1][1] + fsuf[i][sz + up - 1]) % P;
}
}
sz += siz[to[i]];
}
if (s.count(sz)) f[u][1][sz] = (f[u][1][sz] + 1) % P;
if (deg && siz[to[deg]] < sz - 1) {
if (s.count(sz - 1)) f[u][1][sz - 1] = (f[u][1][sz - 1] + 1) % P;
}
if (deg && siz[to[deg]] < sz + up - 1) {
if (s.count(sz + up - 1)) ans[sz + up - 1][1] = (ans[sz + up - 1][1] + 1) % P;
}
if (deg) {
int kk = 0, prod = 1;
int lst = 0;
sz = siz[to[1]];
for (int i = 1; i <= deg; i++) {
int v = to[i];
if (siz[v] != sz) {
lst = i;
break;
}
kk = (prod + 1ll * kk * f[v][1][sz]) % P;
prod = 1ll * prod * f[v][1][sz] % P;
}
if (lst) kk = 1ll * kk * fsuf[lst][sz] % P;
if (s.count(sz)) f[u][1][sz] = (f[u][1][sz] + kk) % P;
if (u == 1 && s.count(sz)) {
ans[sz][1] = (ans[sz][1] + kk) % P;
}
}
}
} else {
int vv = 0;
for (int v : e[u]) if (v != pre) {
vv = v;
break;
}
if (!vv) {
for (int t : s) f[u][0][t] = f[u][1][t] = 1;
} else {
for (int k = 0; k <= 1; k++) {
for (auto p : f[vv][k]) {
int t = p.first;
f[u][k][t] = 1;
for (int v : e[u]) if (v != pre) {
f[u][k][t] = 1ll * f[u][k][t] * f[v][k][t] % P;
}
}
}
}
}
}
} t;
struct Graph {
vector<int> e[MAXN];
void add(int u, int v) {
e[u].push_back(v), e[v].push_back(u);
}
int low[MAXN], dfn[MAXN], dcnt;
stack<int> s;
void tarjan(int u) {
low[u] = dfn[u] = ++dcnt;
s.push(u);
for (int v : e[u]) if (!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
if (low[v] >= dfn[u]) {
int id = ++t.tot;
while (1) {
int w = s.top(); s.pop();
t.add(id, w);
if (w == v) break;
}
t.add(id, u);
}
} else {
low[u] = min(low[u], dfn[v]);
}
}
} g;
int main() {
// freopen("cities.in", "r", stdin);
// freopen("cities.out", "w", stdout);
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= m; i++) {
int u, v; scanf("%d%d", &u, &v);
g.add(u, v);
}
t.tot = n;
g.tarjan(1);
for (int i = 2; i <= n; i++)
s.insert(n / i);
t.dfs(1, 0);
int fans = 0;
if (k == 0) {
for (int i : s) if (n % i == 0) {
fans = (fans + ans[i][0]) % P;
}
} else {
for (int i : s) {
fans = (fans + ans[i][1]) % P;
}
for (int i : s) if (s.count(i - 1)) {
fans = (fans - ans[i][0] + P) % P;
}
}
printf("%d\n", fans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通