CF1799H Tree Cutting 解题报告
给你一个有 个点的树和一个长度为 的序列 ,你要执行以下操作 次:
-
选出目前存在的一条边。
-
删除这条边。
-
选出分裂出的两个联通块的其中一块。
-
删除这个联通块,并写下剩余的联通块的大小。
问有多少种不同的操作方案使得写下的序列等于给出的序列,答案对 取模。
。
没看懂官方题解在说什么,于是自己搞了一个做法,复杂度比标算劣一些。
考虑操作 到其父亲的边,一种可能是保留以 为根的子树,一种可能是删除以 为根的子树。如果是后者,将符合题意的条件从保留的联通块大小为 ,改成删去大小为 的联通块,容易发现这是等价的。
这样转化的好处是,每次操作都是对一个子树操作,方便树形 dp。
状压,记 表示以 为根的子树,子树内最早的保留子树操作是 (如果不存在,则认为 ),操作的是以 为根的子树的子树的集合为 。
合并两个子树的时候,要求至多有一个子树满足 ,且两个子树的集合 满足 。
在合并完所有儿子后,再考虑操作 的子树,枚举对应的操作 。
如果这是一个保留子树的操作,则要求 ;如果是一个删除子树的操作,则要求 。同时,可以简单计算出操作 之前 的子树大小,从而判断出是否满足条件。
时间复杂度 ,可以通过高维前缀和优化到 ,但是没有必要。
需要注意的是,代码中的 是从 开始编号的,所以上述的值部分要减一。
#include<bits/stdc++.h>
#define IL inline
#define reg register
#define N 5050
#define mod 998244353
IL int read()
{
reg int x=0; reg char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x;
}
IL int Add(reg int x,reg int y){return x+y<mod?x+y:x+y-mod;}
IL void Pls(reg int &x,reg int y){x=Add(x,y);}
IL int Mul(reg int x,reg int y){reg long long r=1ll*x*y; return r<mod?r:r%mod;}
int n,k,c[7],d[7];
std::vector<int>G[N];
IL void add(reg int u,reg int v){G[u].push_back(v),G[v].push_back(u);}
int full,f[N][7][1<<6|5],high[1<<6|5],sum[1<<6|5],sz[N],ans;
void dfs(reg int u,reg int fa=0)
{
sz[u]=1;
for(reg auto v:G[u])if(v!=fa)dfs(v,u),sz[u]+=sz[v];
static int g[7][1<<6|5]; f[u][k][0]=1;
for(reg auto v:G[u])if(v!=fa)
{
for(reg int j=0,s;j<=k;++j)for(s=0;s<=full;++s)g[j][s]=0;
for(reg int s1=0,s2,s;s1<=full;++s1)for(s=s1;s<=full;s=(s+1)|s1)
s2=s^s1,Pls(g[k][s],Mul(f[u][k][s1],f[v][k][s2]));
for(reg int j=k,s1,s2,s;j--;)for(s1=0;s1<=full;++s1)if(f[u][j][s1]||f[v][j][s1])for(s=s1;s<=full;s=(s+1)|s1)
{
if(high[s2=s^s1]>j)continue;
Pls(g[j][s],Mul(f[u][j][s1],f[v][k][s2])),Pls(g[j][s],Mul(f[v][j][s1],f[u][k][s2]));
}
for(reg int j=0,s;j<=k;++j)for(s=0;s<=full;++s)f[u][j][s]=g[j][s];
}
if(u==1)return;
for(reg int j=0,s;j<=k;++j)for(s=0;s<=full;++s)g[j][s]=0;
for(reg int j=0,l,s,S,x;j<=k;++j)for(s=0;s<=full;++s)if(f[u][j][s])for(l=0;l<j;++l)if(~s>>l&1)
{
S=s&(1<<l)-1,x=sz[u]-sum[S];
if(x==c[l])Pls(g[l][s^(1<<l)],f[u][j][s]);
if(x==d[l]&&l>high[s])Pls(g[j][s^(1<<l)],f[u][j][s]);
}
for(reg int j=0,s;j<=k;++j)for(s=0;s<=full;++s)Pls(f[u][j][s],g[j][s]);
}
main()
{
n=read();
for(reg int i=n;--i;add(read(),read()));
k=read(),full=(1<<k)-1;
for(reg int i=0;i<k;++i)c[i]=read(),d[i]=i?c[i-1]-c[i]:n-c[i];
high[0]=-1;
for(reg int s=0,i;s<=full;++s)for(i=0;i<k;++i)if(s>>i&1)high[s]=i;
for(reg int s=1;s<=full;++s)sum[s]=sum[s^(1<<high[s])]+d[high[s]];
dfs(1);
for(reg int i=0;i<=k;++i)Pls(ans,f[1][i][full]);
printf("%d\n",ans);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通