@noi.ac - 442@ 牛羊被他抢了
@description@
众所周知小G热衷于搏弈,有一天他来到你的大草原上,抢走了你所有的牛羊,以供他搏弈。
你大惊失措,赶紧来到小G家拯救你的牛羊,然而,当你赶到的时候,你的牛已经变成了搏弈牛,你的羊已经变成了搏弈羊。你怒不可遏,决定拆了小G的家。
小G的家有 n 个房间,n−1 条走廊连接这些房间使得房间之间两两可达。在每一间房间里可能有一只牛,或者一只羊,或者一只小G,注意由于小G会分身术,所以可以有多个小G。
经过慎重考虑,你决定选择一些房间来攻击,你选择的房间必须满足两两之间可以不通过你未选择的房间到达。当你攻击时,你选择的房间中的所有牛、羊和小G都会来与你搏弈,由于人的力量是有限的,你只能应对 A 只牛,B 只羊,C 只小G。也就是说你能够同时应对 u 只牛,v 只羊和 w 只小G当且仅当 u≤A 且 v≤B 且 w≤C。
你想知道有多少种选择房间的方案使得你可以应对他们,输出其对 998244353 取模的结果。
@Input format@
第一行四个数 n,A,B,C,含义如题。
第二行 n 个数,第 i 个数表示第 i 间房里是什么:0 代表牛,1 代表羊,2 代表小G。
接下来 n−1 行,每行两个数 u,v,表示 u 到 v 之间有一条走廊。
@Output format@
一个数表示答案。
@Sample input@
5 1 1 1
0 1 0 0 2
1 2
1 3
3 4
4 5
@Sample output@
7
@Constraints@
对于所有的数据,n≤200,A+B+C≤n/2。
@solution@
任轩笛的论文《解决树上连通块问题的一些技巧和工具》中有提到与这道题类似的题并给出了解决方案:
对于一类树上连通块DP问题,如果信息的合并不够高效,但加入单点的信息比较高效,往往可以按照DFS序转移,把合并子树变成添加单点。
先假定选出的连通块必须包含根,求出整棵树的DFS序,一个包含根的树上连通块的结构一定形如整棵树去掉若干个互不相交的子树。在DFS序中,去掉的就是连续的若干段。
设 dp[i] 表示考虑了DFS序前 i 个节点时的信息。如果要选择第 i 个节点,则转移到 dp[i+1],否则转移到 dp[j],其中 j 是第 i 个点子树DFS序的右端点+1,表示去掉这整个子树。
而对于选出的连通块可以不包含根的问题,只要进行点分治,每次把重心作为根,算
出强制包含重心的方案数,再把重心删除,对每个连通块递归做。
之后论文中给出的例题也与这道题大同小异。
题意:给出一棵树,每个点有个颜色。给出3种颜色 u, v, w ,求有多少个树上连通块满足里面颜色为 u, v, w 的点的个数分别为 a, b, c 。
做法:用 dp[x][i][j][k] 表示处理了以 x 为根的子树,三种颜色个数分别为 i, j, k 的方案数。如果使用三维FFT优化,复杂度能变成 O(nabc log abc) ,但是常数和码量都较大。考虑使用上述的点分治算法,配合按DFS序转移,复杂度为 O(nabc log n)。
然后论文的题解基本照搬过来即可。
因为 A + B + C <= n/2 <= 100,所以 A*B*C 最大时 A = B = C,可以得到 A*B*C 最大约可以取 3*10^4。
所以 O(nABC*logn) 是可以跑得过的。
所以这是一道论文题。
@accepted code@
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 200;
const int MOD = 998244353;
struct edge{
int to; edge *nxt;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt=&edges[0];
void addedge(int u, int v) {
edge *p = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
p = (++ecnt);
p->to = u, p->nxt = adj[v], adj[v] = p;
}
int siz[MAXN + 5], hvy[MAXN + 5], dfn[MAXN + 5], dcnt;
int a[MAXN + 5], n, A, B, C;
bool vis[MAXN + 5];
void dfs(int x, int fa) {
dfn[++dcnt] = x, siz[x] = 1, hvy[x] = 0;
for(edge *p=adj[x];p;p=p->nxt) {
if( vis[p->to] || p->to == fa ) continue;
dfs(p->to, x);
siz[x] += siz[p->to];
hvy[x] = max(hvy[x], siz[p->to]);
}
}
int GetG(int x, int fa, int tot) {
int ret = x; hvy[x] = max(hvy[x], tot - siz[x]);
for(edge *p=adj[x];p;p=p->nxt) {
if( vis[p->to] || p->to == fa ) continue;
int tmp = GetG(p->to, x, tot);
if( hvy[tmp] < hvy[ret] )
ret = tmp;
}
return ret;
}
int dp[MAXN + 5][34*34*35 + 5], ans = 0;
inline int id(int x, int y, int z) {
return x*(B+1)*(C+1) + y*(C+1) + z;
}
void divide(int x) {
vis[x] = true, dcnt = 0, dfs(x, -1);
for(int p=1;p<=dcnt+1;p++)
for(int i=0;i<=A;i++)
for(int j=0;j<=B;j++)
for(int k=0;k<=C;k++)
dp[p][id(i, j, k)] = 0;
dp[1][id(0, 0, 0)] = 1;
for(int p=1;p<=dcnt;p++)
for(int i=0;i<=A;i++)
for(int j=0;j<=B;j++)
for(int k=0;k<=C;k++) {
dp[p+siz[dfn[p]]][id(i, j, k)] = (dp[p+siz[dfn[p]]][id(i, j, k)] + dp[p][id(i, j, k)])%MOD;
if( a[dfn[p]] == 0 && i != A ) dp[p+1][id(i+1, j, k)] = (dp[p+1][id(i+1, j, k)] + dp[p][id(i, j, k)])%MOD;
if( a[dfn[p]] == 1 && j != B ) dp[p+1][id(i, j+1, k)] = (dp[p+1][id(i, j+1, k)] + dp[p][id(i, j, k)])%MOD;
if( a[dfn[p]] == 2 && k != C ) dp[p+1][id(i, j, k+1)] = (dp[p+1][id(i, j, k+1)] + dp[p][id(i, j, k)])%MOD;
}
for(int i=0;i<=A;i++)
for(int j=0;j<=B;j++)
for(int k=0;k<=C;k++)
ans = (ans + dp[dcnt + 1][id(i, j, k)])%MOD;
ans = (ans + MOD - 1)%MOD;
for(edge *p=adj[x];p;p=p->nxt)
if( !vis[p->to] ) divide(GetG(p->to, -1, siz[p->to]));
}
int main() {
scanf("%d%d%d%d", &n, &A, &B, &C);
for(int i=1;i<=n;i++)
scanf("%d", &a[i]);
for(int i=1;i<n;i++) {
int u, v; scanf("%d%d", &u, &v);
addedge(u, v);
}
dcnt = 0, dfs(1, -1), divide(GetG(1, -1, siz[1]));
printf("%d\n", ans);
}
@details@
所以多读书论文是有好处的。
只不过我估计 noi 上是不会出现这种题的吧。。。