题目
给定一棵树,你需要删去一些边(可以不删),使得剩下的图中每个点所在的连通块大小都(geq k)。
求删边的方案数,对(786433)取模。两种方案不同,当且仅当存在一条边在一个方案中被删去,而在另外一个方案中没有被删去。
分析
设(dp[x][k])表示以点(x)为根的子树中(x)所在的连通块大小为(k)的方案数
状态转移方程显然,每次都要计算断了一条边的贡献,当且仅当子节点所在的连通块大小不小于(k)
但是这样(O(n^3))会T掉,考虑当中有很多冗余状态,那么记录子树大小,按照子树大小拼接,时间复杂度(O(n^2))
代码
#include <cstdio>
#include <cctype>
#define rr register
using namespace std;
const int N = 5011, mod = 786433;
struct node {
int y, next;
} e[N << 1];
int dp[N][N], son[N], f[N], ans, as[N], n, k = 1, m;
inline signed iut() {
rr int ans = 0;
rr char c = getchar();
while (!isdigit(c)) c = getchar();
while (isdigit(c)) ans = (ans << 3) + (ans << 1) + (c ^ 48), c = getchar();
return ans;
}
inline signed mo(int x, int y) { return x + y >= mod ? x + y - mod : x + y; }
inline void dfs(int x, int fa) {
dp[x][1] = son[x] = 1;
for (rr int i = as[x], s; i; i = e[i].next)
if (e[i].y != fa) {
dfs(e[i].y, x), son[x] += son[e[i].y], s = 0;
for (rr int j = 1; j <= son[x]; ++j) f[j] = 0;
for (rr int j = 1; j <= son[x] - son[e[i].y]; ++j)
for (rr int o = 1; o <= son[e[i].y]; ++o)
f[j + o] = mo(f[j + o], 1ll * dp[x][j] * dp[e[i].y][o] % mod);
for (rr int o = m; o <= son[e[i].y]; ++o) s += dp[e[i].y][o];
for (rr int j = 1; j <= son[x]; ++j) dp[x][j] = mo(1ll * dp[x][j] * s % mod, f[j]);
}
}
signed main() {
freopen("cut.in", "r", stdin);
freopen("cut.out", "w", stdout);
n = iut();
m = iut();
for (rr int i = 1; i < n; ++i) {
rr int x = iut(), y = iut();
e[++k] = (node){ y, as[x] }, as[x] = k;
e[++k] = (node){ x, as[y] }, as[y] = k;
}
dfs(1, 0);
for (rr int i = m; i <= son[1]; ++i) ans = mo(ans, dp[1][i]);
return !printf("%d", ans);
}