NOI 2026 Day 2 T3 题解
设
可以发现转移到的点就是
最后还需要加入点
为了避免算重,只需要把上面的四个状态中的
但是这样会算漏,因为即使
但是这样又算重了,为什么呢?因为实际上如果两种转移
考虑怎么处理这个问题,我们可以使用类似延迟钦定的技巧,因为一个子树内的任何一个点不论是否在集合内,都可以与祖先有相同的颜色。我们将不在集合内的点与祖先的匹配延迟考虑,或者说在固定
现在转移就不一样了,我们转移到的点变成了一个矩形的左边界和上边界,这样只需要记三个顶点同样可以前缀和维护。
还有一个小问题,就是我们刚才说的延迟匹配,也就是如果要合并
这样我们就以
#include <bits/stdc++.h>
#include "rainbow.h"
using namespace std;
const int mod = 998244353;
inline void inc(int &x, int y) { x = (x + y >= mod ? x + y - mod : x + y); }
inline void dec(int &x, int y) { x = (x < y ? x - y + mod : x - y); }
inline int add(int x, int y) { return (x + y >= mod ? x + y - mod : x + y); }
vector<int> v[210];
int siz[210], dp[210][210][210], f[210][210][210][3], g[210][210][3], s1[210][210], s2[210][210];
void dfs(int x)
{
siz[x] = 1;
f[x][1][0][0] = 1;
f[x][1][0][1] = 1;
f[x][1][0][2] = 1;
for (auto y : v[x])
{
dfs(y);
for (int ix = 0; ix <= siz[x]; ix++) for (int jx = 0; ix + jx <= siz[x]; jx++) for (int iy = 0; iy <= siz[y]; iy++) for (int jy = 0; iy + jy <= siz[y]; jy++)
{
if (ix <= iy) inc(g[iy][max(jx - (iy - ix), jy)][0], 1ll * f[x][ix][jx][0] * dp[y][iy][jy] % mod);
else inc(g[ix][max(jx, jy - (ix - iy))][0], 1ll * f[x][ix][jx][0] * dp[y][iy][jy] % mod);
inc(g[max(ix, iy)][jx + jy][1], 1ll * f[x][ix][jx][1] * dp[y][iy][jy] % mod);
inc(g[ix + iy][jx + jy][2], 1ll * f[x][ix][jx][2] * dp[y][iy][jy] % mod);
}
siz[x] += siz[y];
for (int i = 0; i <= siz[x]; i++) for (int j = 0; i + j <= siz[x]; j++)
{
f[x][i][j][0] = g[i][j][0], g[i][j][0] = 0;
f[x][i][j][1] = g[i][j][1], g[i][j][1] = 0;
f[x][i][j][2] = g[i][j][2], g[i][j][2] = 0;
}
}
for (int i = 0; i <= siz[x]; i++) for (int j = 0; i + j <= siz[x]; j++) s1[i][j] = s2[i][j] = 0;
for (int i = 0; i <= siz[x]; i++) for (int j = 0; i + j <= siz[x]; j++)
{
inc(s1[i][j], f[x][i][j][0]);
dec(s1[i][j], f[x][i][j][1]);
inc(s2[i][j], f[x][i][j][1]);
dec(s2[i + 1][j], f[x][i][j][2]);
}
for (int i = 0; i <= siz[x]; i++) for (int j = 1; i + j <= siz[x]; j++) inc(s1[i][j], s1[i][j - 1]);
for (int i = 1; i <= siz[x]; i++) for (int j = 0; i + j <= siz[x]; j++) inc(s2[i][j], s2[i - 1][j]);
for (int i = 0; i <= siz[x]; i++) for (int j = 0; i + j <= siz[x]; j++)
{
int val = add(s1[i][j], s2[i][j]);
inc(dp[x][i][j], val);
if (i >= 1) inc(dp[x][i - 1][j + 1], val);
}
}
int rainbow(int c, int n, vector<int> fa)
{
for (int i = 1; i < n; i++) v[fa[i] + 1].push_back(i + 1);
dfs(1);
int ans = 0;
for (int i = 0; i <= n; i++) inc(ans, dp[1][0][i]);
return ans;
}
后记
赛时我在这道题上花费了两个小时,但是最终发现会算重放弃了这道题,获得了
这两天我一直在思考如果我不去做 T3 而是拼 T2 暴力或者冲 T2 正解会怎么样?似乎肯定会得到更高的分数,甚至可能接近或达到 Au 线。
回过头来再看这道题,我才意识到我的场上做法距离正解其实只有一步,可当时我的心态已经接近崩溃,没有办法进行有效的思考了。
即便如此,我并不后悔当时跳过 T2 做 T3 的这个决定。某种意义上,我是幸运的——我遵从了那一刻的直觉,赌过一次,并且已经无限接近胜利,只是最后差了那么一点运气。
也许,这正是 OI 最残忍也最迷人的地方:你永远不知道那一步之外是什么,但你也永远会为迈出那一步而心动。