题解 [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;
}
posted @ 2022-05-16 21:45  Administrator-09  阅读(2)  评论(0编辑  收藏  举报