【UR #1】跳蚤国王下江南
题目链接
做法
建出圆方树,考虑行走的路径一定是树形结构,每个环都有一个节点深度是最浅的,遇到环时会有两种走法。考虑DP。令 $ dp[i][j] $ 表示 $ i $ 往下走 $ j $ 步的方案数,可以由 $ i $ 在圆方树上的儿子节点转移(方点和圆点要分别讨论),最后求的是 $ dp[1][j] $ ,可以用生成函数计算。
考虑点分治转移DP。对于一个连通块,取离1号点最近的节点 root 作为连通块的根节点。取连通块的中心 g , 计算 g 的子树(包括 g)对 root 的转移。转移包括求 $ dp[g] $ 和 g 到 root 的方案数 $ f[g] $ ,进行卷积。先将这个连通块中的 g 去掉,递归求出原连通块里与 g 相邻的连通块的答案,再将 $ dp[g] $ 通过 g 的儿子节点求出,$ f[g] $ 可由 g 父亲所属的连通块转移得到。
由于点分治一次至少将点集划为一半,圆方树的点数 $ O(n) $ ,则递归时间复杂度 $ T(n) = 2T(\frac{n}{2}) + O(n \log n) = O(n \log^2 n) $ ,其中 $ f[g] $ 转移的时间 $ t(n) = t(n / 2) + O(n \log n) = O(n \log n) $ 。
#include <bits/stdc++.h>
#define fst first
#define snd second
#define pb push_back
using namespace std;
typedef pair<int, int> pii;
typedef long long ll;
const int mod = 998244353;
inline int add(const int &x, const int &y) { return x + y < mod ? x + y : x + y - mod; }
inline int sub(const int &x, const int &y) { return x - y < 0 ? x - y + mod : x - y; }
inline int mul(const int &x, const int &y) { return (int)((ll)x * y % mod); }
inline int ksm(int x, int y = mod - 2) {
int ss = 1; for(; y; y >>= 1, x = mul(x, x)) if(y & 1) ss = mul(ss, x);
return ss;
}
inline int Get(int x) { int ss = 1; for(; ss <= x; ss <<= 1); return ss; }
namespace Poly {
inline int Get(int x) { int ss = 1; for(; ss <= x; ss <<= 1); return ss; }
void ntt(vector<int> &A, int lmt, int opt) {
A.resize(lmt + 5);
for(int i = 0, j = 0; i < lmt; i++) {
if(i > j) swap(A[i], A[j]);
for(int k = lmt >> 1; (j ^= k) < k; k >>= 1);
}
vector<int> w(lmt >> 1);
for(int mid = 1; mid < lmt; mid <<= 1) {
w[0] = 1;
int w0 = ksm(opt == 1 ? 3 : (mod + 1) / 3, (mod - 1) / mid / 2);
for(int j = 1; j < mid; j++) w[j] = mul(w[j - 1], w0);
for(int R = mid << 1, j = 0; j < lmt; j += R)
for(int k = 0; k < mid; k++) {
int x = A[j + k], y = mul(w[k], A[j + mid + k]);
A[j + k] = add(x, y), A[j + mid + k] = sub(x, y);
}
}
if(opt == -1)
for(int i = 0, inv = ksm(lmt); i < lmt; i++) A[i] = mul(A[i], inv);
}
vector<int> Mul(const vector<int> &a, const vector<int> &b) {
vector<int> A = a, B = b; int lmt = Get(a.size() + b.size() - 2);
ntt(A, lmt, 1), ntt(B, lmt, 1);
for(int i = 0; i < lmt; i++) A[i] = mul(A[i], B[i]);
ntt(A, lmt, -1); return A.resize(a.size() + b.size() - 1), A;
}
vector<int> Add(const vector<int> &a, const vector<int> &b) {
vector<int> A = a, B = b; int len = max(a.size(), b.size());
A.resize(len), B.resize(len);
for(int i = 0; i < len; i++) A[i] = (A[i] + B[i]) % mod;
return A;
}
}
using Poly::Mul;
using Poly::Add;
const int N = 300010;
int n, m;
vector<pii> E[N]; vector<int> e[N];
map<int, int> ma[N];
int dfn[N], low[N], idx = 0, sta[N], top = 0;
int tot = 0, posb[N], len[N], father[N], type[N];
// type == 1 : not belong to the circle above
void get_circle(int u, int v) {
++tot;
for(;;) {
int x = sta[top--];
e[x].pb(tot), e[tot].pb(x);
++len[tot], posb[x] = len[tot], father[x] = tot;
if(x == v) break;
}
type[v] = (len[tot] == 1 && ma[u][v] == 1);
e[u].pb(tot), e[tot].pb(u);
++len[tot], father[tot] = u;
}
void tarjan(int u, int ff) {
dfn[u] = low[u] = ++idx, sta[++top] = u;
for(auto v : E[u]) if(v.snd != ff) {
if(!dfn[v.fst]) {
tarjan(v.fst, v.snd), low[u] = min(low[v.fst], low[u]);
if(low[v.fst] >= dfn[u]) get_circle(u, v.fst);
}
else low[u] = min(low[u], dfn[v.fst]);
}
}
bool vis[N];
int sz[N], mx[N], anc[N], root, size;
vector<int> f[N], g[N], t;
int get_size(int u, int ff) {
int res = 1;
for(auto v : e[u]) if(v != ff && !vis[v]) res += get_size(v, u);
return res;
}
void getrt(int u, int ff) {
sz[u] = 1, mx[u] = 0;
for(auto v : e[u]) if(v != ff && !vis[v]) {
getrt(v, u), sz[u] += sz[v];
mx[u] = max(mx[u], sz[v]);
}
mx[u] = max(mx[u], size - sz[u]);
if(!root || mx[u] < mx[root]) root = u;
}
void solve_point(int u) {
t.resize(1, 1);
for(auto v : e[u]) if(father[u] != v && !vis[v]) t = Add(t, f[v]);
}
void solve_circle(int u) {
for(auto v : e[u]) if(father[u] != v && !vis[v]) {
int dis1 = len[u] - posb[v], dis2 = posb[v];
t.resize(max(t.size(), f[v].size() + max(dis1, dis2)));
for(int i = 0; i < (int)f[v].size(); i++) {
t[i + dis1] = (t[i + dis1] + f[v][i]) % mod;
if(!type[v]) t[i + dis2] = (t[i + dis2] + f[v][i]) % mod;
}
}
}
void jump(int u, int v) {
// link v -> father[v]
if(v > n) return ;
int dis1 = len[father[v]] - posb[v], dis2 = posb[v];
g[u].resize(max(g[u].size(), g[u].size() + max(dis1, dis2)));
for(int i = g[u].size() - 1; ~i; i--) {
g[u][i] = 0;
if(i >= dis1) g[u][i] = (g[u][i] + g[u][i - dis1]) % mod;
if(i >= dis2 && !type[v]) g[u][i] = (g[u][i] + g[u][i - dis2]) % mod;
}
}
void build(int u) {
root = 0, size = get_size(u, 0), getrt(u, 0);
int now = root; vis[now] = 1, anc[now] = u;
// printf(">>> %d -> %d\n", now, u);
if(now != u) build(u);
for(auto v : e[now]) if(!vis[v] && father[now] != v) {
build(v);
}
t.clear();
if(now <= n) solve_point(now);
else solve_circle(now);
g[now].resize(1, 1);
for(int x = now; x != u; x = anc[father[x]]) {
jump(now, x), g[now] = Mul(g[now], g[father[x]]);
}
vector<int> tmp = Mul(t, g[now]);
// printf(">>> %d -> %d : \n", now, u);
// printf(">>> "); for(auto v : t) printf("%d ", v); puts("");
// printf(">>> "); for(auto v : g[now]) printf("%d ", v); puts("");
// puts("");
f[u] = Add(f[u], tmp);
vis[now] = 0;
}
int main() {
scanf("%d%d", &n, &m), tot = n;
for(int i = 1; i <= m; i++) {
int x, y; scanf("%d%d", &x, &y);
E[x].pb({y, i}), E[y].pb({x, i}), ++ma[x][y], ++ma[y][x];
}
tarjan(1, 0);
// for(int i = 1; i <= tot; i++) printf("father[%d] = %d : %d %d\n", i, father[i], len[i], posb[i]);
build(1);
f[1].resize(n);
for(int i = 1; i < n; i++) printf("%d\n", f[1][i]);
return 0;
}