【集训队作业2018】普通的计数题 DP+分治NTT

48 篇文章 0 订阅
3 篇文章 0 订阅

Description
你有一个 01 01 01序列,初始时序列为空。你可以对序列进行两种操作:
1.在序列末端插入一个 0 0 0
2.在序列中删去一个子序列,并在序列末端插入一个 1 1 1。这里对子序列的选取有一定限制,设子序列中包含 x x x 0 0 0 y y y 1 1 1,则你选取的子序列必须满足:
1.子序列不可为空,即 x + y > 0 x+y>0 x+y>0
2.当 y > 0 y>0 y>0时, x ∈ A x\in A xA,这里 A A A为给定集合
3.当 y = 0 y=0 y=0时, x ∈ B x\in B xB,这里 B B B为给定集合

现在,你需要对序列执行 n n n次操作。请你求出在所有不同的操作方案中,最终序列长度为 1 1 1的方案有多少种。两种操作方案被视为不同,当且仅当某一次操作的种类不同,或某个第二类操作中选取的子序列不同(子序列不同指的是位置不同,与值无关)。


Sample Input
4 1 1
1
1


Sample Output
3


一共有 n n n次操作,每个操作新加一个点,所以总共有 n n n个点。
设第 i i i次加入的点为 s i si si,删除它的时候加入的点为它的父亲,于是就可以构成一棵树的结构, 1 1 1是非叶节点, 0 0 0是叶子节点。
于是就是让你求这样一棵树的方案数。
于是你考虑 D P DP DP
f [ i ] f[i] f[i]为大小 i i i的合法的树的方案数, g [ i ] g[i] g[i]为大小 i i i的合法森林的方案数,可得转移:
f ( n ) = ∑ i ∈ A C n − 1 i − 1 g ( n − i − 1 ) + [ n − 1 ∈ B ] f(n)=\sum_{i\in A}C_{n-1}^{i-1}g(n-i-1)+[n-1\in B] f(n)=iACn1i1g(ni1)+[n1B]
g ( n ) = ∑ i = 1 n − 1 C n − 1 i − 1 g ( i ) f ( n − i ) g(n)=\sum_{i=1}^{n-1}C_{n-1}^{i-1}g(i)f(n-i) g(n)=i=1n1Cn1i1g(i)f(ni)
于是你可以用分治NTT。


#include <ctime>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>

using namespace std;
typedef long long LL;
int _max(int x, int y) {return x > y ? x : y;}
int _min(int x, int y) {return x < y ? x : y;}
const LL mod = 998244353;
const int N = 120001;
int read() {
	int s = 0, f = 1; char ch = getchar();
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * f;
}
void put(int x) {
	if(x >= 10) put(x / 10);
	putchar(x % 10 + '0');
}

int n, alen, blen;
LL w1[N * 4], w2[N * 4];
LL f[N * 4], g[N * 4], hh[N], jc[N], inv[N];
LL A[N * 4], B[N * 4], C[N * 4], D[N * 4];
int R[4 * N];

LL pow_mod(LL a, int k) {
	LL ans = 1;
	while(k) {
		if(k & 1) (ans *= a) %= mod;
		(a *= a) %= mod; k /= 2;
	} return ans;
}

void pre() {
	jc[0] = inv[0] = 1; for(int i = 1; i <= n; i++) jc[i] = jc[i - 1] * i % mod;
	inv[n] = pow_mod(jc[n], mod - 2); for(int i = n - 1; i >= 1; i--) inv[i] = inv[i + 1] * (i + 1) % mod;
	for(int i = 1; i < N * 4; i <<= 1) {
		w1[i] = pow_mod(3, (mod - 1) / (i * 2));
		w2[i] = pow_mod(w1[i], mod - 2);
	}
}

void NTT(LL y[], int len, int on) {
	for(int i = 0; i < len; i++) R[i] = (R[i >> 1] >> 1) | ((i & 1) * (len >> 1));
	for(int i = 0; i < len; i++) if(i < R[i]) swap(y[i], y[R[i]]);
	for(int i = 1; i < len; i <<= 1) {
		LL wn = w1[i]; if(on == -1) wn = w2[i];
		for(int j = 0; j < len; j += i * 2) {
			LL w = 1;
			for(int k = 0; k < i; k++, (w *= wn) %= mod) {
				LL u = y[j + k], v = y[j + k + i] * w % mod;
				y[j + k] = (u + v) % mod, y[j + k + i] = (u - v + mod) % mod;
			}
		}
	} if(on == -1) {
		LL hh = pow_mod(len, mod - 2);
		for(int i = 0; i < len; i++) y[i] = y[i] * hh % mod;
	}
}

void solve(int l, int r){
	if(l == r) {
		if(l > 1) (g[l] += f[l] - f[1] * g[l - 1]) %= mod;
		return ;
	}
	int mid = (l + r) / 2;
	solve(l, mid);
	int N = r - l, len = 1;
	for(len = 1; len <= N; len <<= 1) ;
	for(int i = 0; i < len; i++) A[i] = B[i] = 0;
	for(int i = 0; i <= r - l; i++) A[i] = hh[i];
	for(int i = l; i <= mid; i++) B[i - l] = g[i] * inv[i] % mod;
	NTT(A, len, 1), NTT(B, len, 1);
	for(int i = 0; i < len; i++) A[i] = A[i] * B[i] % mod;
	NTT(A, len, -1);
	for(int i = mid + 1; i <= r; i++) (f[i] += A[i - l - 1] * jc[i - 1]) %= mod;
	if(l == 1) {
		for(int i = 0; i < len; i++) A[i] = 0;
		for(int i = l; i <= mid; i++) A[i - 1] = f[i] * inv[i - 1] % mod;
		NTT(A, len, 1);
		for(int i = 0; i < len; i++) A[i] = A[i] * B[i] % mod;
		NTT(A, len, -1);
		for(int i = mid + 1; i <= r; i++) (g[i] += A[i - l - 1] * jc[i - 1]) %= mod;
	} else {
		for(int i = 0; i < len; i++) A[i] = C[i] = D[i] = 0;
		for(int i = 1; i <= r - l; i++) A[i - 1] = f[i] * inv[i - 1] % mod, C[i - 1] = g[i] * inv[i] % mod;
		for(int i = l; i <= mid; i++) D[i - l] = f[i] * inv[i - 1] % mod;
		NTT(A, len, 1), NTT(C, len, 1), NTT(D, len, 1);
		for(int i = 0; i < len; i++) A[i] = (A[i] * B[i] + C[i] * D[i]) % mod;
		NTT(A, len, -1);
		for(int i = mid + 1; i <= r; i++) (g[i] += A[i - l - 1] * jc[i - 1]) %= mod;
	} solve(mid + 1, r);
}

int main() {
	n = read(), alen = read(), blen = read(); pre();
	for(int i = 1; i <= alen; i++) {
		int x = read();
		hh[x] = inv[x];
	} for(int i = 1; i <= blen; i++) f[read() + 1] = 1;
	f[0] = g[0] = 0; f[1] = 1; solve(1, n);
	printf("%d\n", (f[n] + mod) % mod);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值