ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【GDOI2020模拟4.4】Permutation (计数+树形.笛卡尔树dp+容斥):

2020-04-04 20:51:41  阅读:415  来源: 互联网

标签:4.4 int siz mo 容斥 段数 ll dp fo


https://gmoj.net/senior/#main/show/6541

\(n<=5000\)

题解:

我感觉我和这种dp无缘,肝了两个小时拿了0分,还不如去写第三题。

首先这种排列,树上的,不难想到树上的笛卡尔树dp。

设\(f[i][j]\)表示\(i\)子树里\(j\)段balabala的。

再看这题,边的限制相当于\(x->fa[x]\)的边权=\(x子树内的段数\),注意这题是一个环。

设\(f[i][j]\)表示\(i\)子树内,有\(j\)段,

我们发现如果段与段之间的顺序没有定,是不好做的,所以定段与段的顺序为一个圆排列。

又为了更加确定,我们设\(i\)在的那段是圆排列的开头。

考虑转移,有x和其一儿子y,要合并(x+已经转移过的儿子)的段和y的段。

注意因为边的限制,y的段是确定的,但x的段数不是确定的。

所以枚举x的段数i,设y有j段,再枚举合并成k段,这样有\(i+j-k\)个相邻的段合并了(注意这合并的两个段必须一个属于x一个属于y)。

因为是把y插到x的空位中(不能插到x的第一段的前面),再选一些地方合并,所以再枚举有x个空位中有u个空位插了y。

那么\(new\_f[x][k]+=f[x][i]*f[y][j]*C_{i}^u*C_{j-1}^{u-1}*C_{2u}^{i+j-k}*j\)

最后的\(*j\)是因为把y循环一下插进去也是要记录答案的。

答案就是\(f[1][0]*n\)。

考虑,现在最大的问题就是不能合并来自相同子树的段,不然就不用枚举u,组合数干过去就好了。

所以容斥这个限制,我们要的是恰好有0个来自相同的子树段合并了,现在容斥为至少\(v\)个,容斥系数就\((-1)^v\)

若原来大小是\(i\),则要划分成\(i-v\)段,系数就是\(C_{i-1}^v\)。

然后再做刚才的dp,就不用枚举u了。

然而你发现复杂度没有变,因为每次要\(siz[x]^2\)的时间去把容斥的系数下传之类的,但是能过随机数据。

这个时候,我们就不用一个一个子节点合并了,直接合并所有的子节点。

因为每个子节点的段数是确定的,所以容斥系数下传复杂度就变成\(siz[x]\)了。

拆一下,设\(p[y]\)为y子节点原来的段数,\(q[y]\)为新的段数,合并所有儿子容斥后的段数再合并后的段数为k。

那么贡献是\((^{\sum q[y]}_{q[y1],q[y2],…})*C_{\sum q[y]+1}^{\sum q[y]+1-k}*\prod(-1)^{p[y]-q[y]}*C_{p[y]-1}^{q[y]}\)

那么这个系数是可以预先拆开,然后用背包合并的。

Code:


#include<bits/stdc++.h> 
#define fo(i, x, y) for(int i = x, _b = y; i <= _b; i ++)
#define ff(i, x, y) for(int i = x, _b = y; i <  _b; i ++)
#define fd(i, x, y) for(int i = x, _b = y; i >= _b; i --)
#define ll long long
#define pp printf
#define hh pp("\n")
using namespace std;

int mo;

ll ksm(ll x, ll y) {
	ll s = 1;
	for(; y; y /= 2, x = x * x % mo)
		if(y & 1) s = s * x % mo;
	return s;
}

const int N = 5005;

int n, x, y, z;
int fi[N], to[N * 2], nt[N * 2], w[N * 2], tot;

void link(int x, int y, int z) {
	nt[++ tot] = fi[x], to[tot] = y, w[tot] = z, fi[x] = tot;
}

int fa[N], dep[N], d[N];
int siz[N];

ll fac[N], nf[N];
ll f[N], g[N], h[N];

int C(int n, int m) {
	if(n < m) return 0;
	return fac[n] * nf[n - m] % mo * nf[m] % mo;
}

void dg(int x) {
	dep[x] = dep[fa[x]] + 1;
	for(int i = fi[x]; i; i = nt[i]) {
		int y = to[i]; if(y == fa[x]) continue;
		fa[y] = x; d[y] = w[i] / 2;
		dg(y);
	}
	siz[x] = 1;
	fo(i, 0, n) g[i] = 0; g[1] = 1;
	for(int i = fi[x]; i; i = nt[i]) {
		int y = to[i]; if(y == fa[x]) continue;
		{
			fo(i, 0, siz[x]) h[i] = g[i], g[i] = 0;
			fo(i, 1, siz[x]) {
				fo(j, 1, d[y]) {
					ll xs = ((d[y] - j) % 2 ? -1 : 1) * C(d[y] - 1, d[y] - j) * f[y] % mo * d[y] % mo * nf[j] % mo;
					g[i + j] = (g[i + j] + h[i] * xs) % mo;
				}
			}
			siz[x] += siz[y];
		}
	}
	f[x] = 0;
	fo(j, max(1, d[x]), siz[x]) {
		f[x] = (f[x] + g[j] * fac[j - 1] % mo * C(j, j - d[x])) % mo;
	}
	f[x] = (f[x] % mo + mo) % mo;
}


int main() {
	freopen("permutation.in", "r", stdin);
	freopen("permutation.out", "w", stdout);
	scanf("%d %d", &n, &mo);
	fo(i, 1, n - 1) {
		scanf("%d %d %d", &x, &y, &z);
		link(x, y, z); link(y, x, z);
		if(z % 2 == 1) {
			pp("0\n"); return 0;
		}
	}
	if(n == 1) {
		pp("1\n");
		return 0;
	}
	fac[0] = 1; fo(i, 1, n) fac[i] = fac[i - 1] * i % mo;
	fo(i, 0, n) nf[i] = ksm(fac[i], mo - 2);
	dg(1);
	ll ans = f[1];
	ans = ans * n % mo;
	pp("%lld\n", ans);
}

标签:4.4,int,siz,mo,容斥,段数,ll,dp,fo
来源: https://www.cnblogs.com/coldchair/p/12634037.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有