题解 [ZJOI2022] 树
一场 4 个小时多点根本不够用,T1 就没怎么看
考虑第一棵树的方案数
按顺序填点的话,一个想法是维护这个点前面有多少个点不是叶子
那么这个点的父亲就有这么多种选法
然而直接这样是不对的,会有钦定某个点不是叶子后面却没有点以它为父亲的情况
那么可以加一维记录有多少个点钦定为非叶子但还没有连边
但是复杂度变 \(O(n^4)\) 了(或者是 \(O(n^5)\)?并未细想)
那么正解:
对这个东西做容斥,容两棵树上分别有多少个点被钦定为非叶子但无儿子
动手拆一下容斥式子发现可以放在一起考虑
那么我来胡一个 dp:
令 \(f_{i, j, k}\) 为即将加入第 \(i\) 个点,左侧(不含 \(i\))有 \(j\) 个非叶子,右侧(包含 \(i\))有 \(k\) 个非叶子的方案数
转移考虑 \(i\) 在 \(T_1\) 中是不是叶子:
- \(T_1\) 中为叶子:
- \(T_2\) 中为非叶子:\(\times j\times (k-1)\to f_{i+1, j, k-1}\)
- \(T_2\) 中为叶子:\(\times-1\times j\times k\to f_{i+1, j, k}\)
- \(T_1\) 中为非叶子:
- \(T_2\) 中为叶子:\(\times j\times k\to f_{i+1, j+1, k}\)
- 在 \(T_2\) 中才是真的非叶子:
这个我一开始推成了:\(\times -1\times j\times (k-1)\to f_{i+1, j, k-1}\)
但实际上这种情况是 本来在 T2 中为非叶子,但我把它当成叶子来统计数量了
所以应该是:\(\times -1\times j\times k\to f_{i+1, j, k}\)
复杂度 \(O(n^3)\)
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 100010
#define ll long long
//#define int long long
char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline ll read() {
ll ans=0, f=1; char c=getchar();
while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
return ans*f;
}
int n;
ll mod;
inline void md(ll& a, ll b) {a+=b; a=a>=mod?a-mod:a;}
namespace force{
ll f[21][1<<20], rec[21][1<<20], ans[21];
void find1() {
memset(f, 0, sizeof(f));
f[1][1]=1;
for (int i=2; i<=n; ++i) {
// cout<<"i: "<<i<<endl;
int lst=i-1, lim=1<<lst;
for (int s=0; s<lim; ++s) {
for (int j=0; j<lst; ++j) {
if (s&(1<<j)) md(f[i][s^(1<<j)^(1<<i-1)], f[i-1][s]);
else md(f[i][s^(1<<i-1)], f[i-1][s]);
}
}
lim=1<<i;
for (int s=0; s<lim; ++s) md(rec[i][s], f[i][s]); //, cout<<f[i][s]<<endl;
}
}
int p[N];
void find2() {
memset(f, 0, sizeof(f));
f[1][1]=1;
for (int i=2; i<=n; ++i) {
for (int j=0; j<i; ++j) p[j]=i-1-j;
int lst=i-1, lim=1<<lst;
for (int s=0; s<lim; ++s) {
for (int j=0; j<lst; ++j) {
if (s&(1<<j)) md(f[i][s^(1<<j)^(1<<i-1)], f[i-1][s]);
else md(f[i][s^(1<<i-1)], f[i-1][s]);
}
}
lim=1<<i;
for (int s=0; s<lim; ++s) {
int t=0;
for (int j=0; j<i; ++j) if (s&(1<<j)) t|=1<<p[j];
t=(lim-1)^t;
ans[i]=(ans[i]+rec[i][t]*f[i][s])%mod;
}
}
}
void solve() {
find1(); find2();
for (int i=2; i<=n; ++i) cout<<ans[i]<<endl;
}
}
namespace task{
int now;
ll f[2][510][510];
void solve() {
for (int i=1; i<=n-1; ++i) f[now][1][i]=i;
puts("1");
for (int i=2; i<n; ++i,now^=1) {
memset(f[now^1], 0, sizeof(f[now^1]));
for (int j=1; j<=i; ++j) {
for (int k=0; k<=n-i+1; ++k) if (f[now][j][k]) {
// printf("f[%d][%d][%d]=%lld\n", i, j, k, f[now][j][k]);
if (k) f[now^1][j][k-1]=(f[now^1][j][k-1]+f[now][j][k]*j*(k-1))%mod;
f[now^1][j][k]=(f[now^1][j][k]-2*f[now][j][k]*j*k)%mod;
f[now^1][j+1][k]=(f[now^1][j+1][k]+f[now][j][k]*j*k)%mod;
// if (k) f[now^1][j][k-1]=(f[now^1][j][k-1]-f[now][j][k]*j*(k-1))%mod;
}
}
ll ans=0;
for (int j=1; j<=i; ++j) ans=(ans+j*f[now^1][j][1])%mod;
printf("%lld\n", (ans%mod+mod)%mod);
}
}
}
signed main()
{
freopen("tree.in", "r", stdin);
freopen("tree.out", "w", stdout);
n=read(); mod=read();
// for (int i=2; i<=n; ++i) printf("%lld\n", force::solve(i));
// force::solve();
task::solve();
return 0;
}