【UR #1】跳蚤国王下江南

题目链接

【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;
}
posted @ 2023-01-22 15:18  daniel14311531  阅读(84)  评论(0编辑  收藏  举报