【九省联考2018】秘密袭击【树形dp】【生成函数】【线段树合并】【多项式插值】

题意: n n n 个点的带点权的树,点权最大值为 w w w,求所有连通子图第 k k k 大权值之和模 64123 64123 64123

n , w ≤ 1666 n,w\leq 1666 n,w1666,时限 5s。

idea 很好的题,可惜被暴力艹过去了。

首先如果点权只有 0 0 0 1 1 1,我们相当于求包含至少 k k k 1 1 1 的连通子图个数。可以设 f ( u , k ) f(u,k) f(u,k) 表示 u u u 为根的可空连通子图中有恰好 k k k 1 1 1 的数量,然后就是刻在 DNA 里的转移方程

f ( u , k ) ⟵ ∑ i = 0 k f ( u , i ) g ( v , k − i ) f(u,k)\longleftarrow\sum_{i=0}^kf(u,i)g(v,k-i) f(u,k)i=0kf(u,i)g(v,ki)

f ( u , 0 ) ⟵ f ( u , 0 ) + 1 f(u,0)\longleftarrow f(u,0)+1 f(u,0)f(u,0)+1

边界: f ( u , v a l u ) = 1 f(u,val_u)=1 f(u,valu)=1

我们枚举值域,大于等于的设为 1 1 1,小于的设为 0 0 0,算出方案数乘上差值,就可以 O ( n 2 w ) \Omicron(n^2w) O(n2w) 踩标算了。

注意到这个转移方程是卷积的形式,所以可以写成生成函数

f u ( x ) = ∏ v ∈ s o n ( u ) f v ( x ) + 1 f_u(x)=\prod_{v\in son(u)}f_v(x)+1 fu(x)=vson(u)fv(x)+1

然后你要求所有点的和,所以开个 g g g 顺便记一下

g u ( x ) = ∑ v ∈ s o n ( u ) g v ( x ) + f u ( x ) − 1 g_u(x)=\sum_{v\in son(u)}g_v(x)+f_u(x)-1 gu(x)=vson(u)gv(x)+fu(x)1

所有的生成函数都是 n n n 次的,所以考虑带 n + 1 n+1 n+1 个点值进去算。因为不是 NTT 模数,所以只能带一般的点值最后再插值回来。

这样还是 O ( n 2 w ) \Omicron(n^2w) O(n2w) 的。带点值这步不好优化,但后面的枚举值域看起来可以优化一下。

可以动态 dp,但常数巨大,还没暴力跑得快。

注意到不同的阀值的计算过程是一样的,可以考虑用线段树合并维护所有阀值的答案。

开个长度为值域的线段树,我们在线段树的每个叶子记录这个阀值的 ( f , g ) (f,g) (f,g),初值为 ( 0 , 0 ) (0,0) (0,0)。你需要维护以下操作:

  1. 开头的 f f f 区间加和区间乘。
  2. 合并两棵线段树, f f f 对应位置相乘, g g g 对应位置相加。
  3. 最后的 f ← f + 1 , g ← g + f f\leftarrow f+1,g\leftarrow g+f ff+1,gg+f

手玩一个标记 ( a , b , c , d ) (a,b,c,d) (a,b,c,d) 表示 ( f , g ) ⟵ ( a f + b , c f + g + d ) (f,g)\longleftarrow (af+b,cf+g+d) (f,g)(af+b,cf+g+d),就可以做了。

关于有懒标记的线段树合并:线段树合并的过程中两边有一边没有儿子时直接返回,这样 pushdown 次数和递归次数同阶,不影响复杂度。

最后插值即可。

复杂度 O ( w n log ⁡ w ) \Omicron(wn\log w ) O(wnlogw)

#include <iostream>
#include <cstring>
#include <cctype>
#include <cstdio>
#include <vector>
#include <algorithm>
#define MAXN 2005
using namespace std;
inline int read()
{
	int ans=0;
	char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
const int MOD=64123;
typedef long long ll;
inline int add(const int& x,const int& y){return x+y>=MOD? x+y-MOD:x+y;}
inline int dec(const int& x,const int& y){return x<y? x-y+MOD:x-y;}
vector<int> e[MAXN];
inline int qpow(int a,int p)
{
	int ans=1;
	while (p)
	{
		if (p&1) ans=(ll)ans*a%MOD;
		a=(ll)a*a%MOD,p>>=1;
	}
	return ans;
}
struct data
{
	int a,b,c,d;
	inline data(const int& a=1,const int& b=0,const int& c=0,const int& d=0):a(a),b(b),c(c),d(d){}
};
inline data operator *(const data& x,const data& y){return data((ll)x.a*y.a%MOD,((ll)y.a*x.b+y.b)%MOD,((ll)x.a*y.c+x.c)%MOD,((ll)x.b*y.c+x.d+y.d)%MOD);}
int n,k,w,val[MAXN];
int ch[MAXN<<5][2],cnt;
data tag[MAXN<<5];
inline void clear()
{
	for (int i=1;i<=cnt;i++) ch[i][0]=ch[i][1]=0,tag[i]=data();
	cnt=0;
}
inline void pushtag(int x,const data& v){tag[x]=tag[x]*v;}
inline void pushdown(int x)
{
	if (!ch[x][0]) ch[x][0]=++cnt;
	if (!ch[x][1]) ch[x][1]=++cnt;
	pushtag(ch[x][0],tag[x]),pushtag(ch[x][1],tag[x]);
	tag[x]=data();
}
int merge(int x,int y)
{
	if (!x||!y) return x|y;
	if (!ch[x][0]&&!ch[x][1]) swap(x,y);
	if (!ch[y][0]&&!ch[y][1])
	{
		tag[x]=tag[x]*data(tag[y].b,0,0,0);
		tag[x]=tag[x]*data(1,0,0,tag[y].d);
		return x;
	}
	pushdown(x),pushdown(y);
	ch[x][0]=merge(ch[x][0],ch[y][0]);
	ch[x][1]=merge(ch[x][1],ch[y][1]);
	return x;
}
void modify(int& x,int l,int r,int ql,int qr,data v)
{
	if (!x) x=++cnt;
	if (ql<=l&&r<=qr) return pushtag(x,v);
	if (qr<l||r<ql) return;
	int mid=(l+r)>>1;
	pushdown(x);
	modify(ch[x][0],l,mid,ql,qr,v),modify(ch[x][1],mid+1,r,ql,qr,v);
}
int querysum(int x,int l,int r)
{
	if (l==r) return tag[x].d;
	int mid=(l+r)>>1;
	pushdown(x);
	return add(querysum(ch[x][0],l,mid),querysum(ch[x][1],mid+1,r));
}
int rt[MAXN];
void dfs(int u,int f,int v)
{
	modify(rt[u],1,w,1,w,data(0,1,0,0));
	modify(rt[u],1,w,1,val[u],data(v,0,0,0));
	for (int i=0;i<(int)e[u].size();i++)
		if (e[u][i]!=f)
		{
			dfs(e[u][i],u,v);
			rt[u]=merge(rt[u],rt[e[u][i]]);
		}
	modify(rt[u],1,w,1,w,data(1,1,1,0));
}
int calc(int v)
{
	clear();
	for (int i=1;i<=n;i++) rt[i]=0;
	dfs(1,0,v);
	return querysum(rt[1],1,w);
}
int ans[MAXN],s[MAXN],tmp[MAXN],f[MAXN];
int main()
{
	n=read(),k=read(),w=read();
	for (int i=1;i<=n;i++) val[i]=read();
	for (int i=1;i<n;i++)
	{
		int u,v;
		u=read(),v=read();
		e[u].push_back(v),e[v].push_back(u);
	}
	for (int v=1;v<=n+1;v++)
		ans[v]=calc(v);
	s[0]=1;
	for (int i=1;i<=n+1;i++)
	{
		for (int j=i;j>=1;j--)
			s[j]=dec(s[j-1],(ll)i*s[j]%MOD);		
		s[0]=(ll)s[0]*(MOD-i)%MOD;
	}
	for (int i=1;i<=n+1;i++)
	{
		int x=ans[i];
		for (int j=1;j<=n+1;j++) if (i!=j) x=(ll)x*qpow(dec(i,j),MOD-2)%MOD;
		tmp[0]=(ll)s[0]*qpow(MOD-i,MOD-2)%MOD;
		for (int j=1;j<=n+1;j++) tmp[j]=(ll)dec(tmp[j-1],s[j])*qpow(i,MOD-2)%MOD;
		for (int j=0;j<=n+1;j++) f[j]=(f[j]+(ll)x*tmp[j])%MOD;
	}
	int res=0;
	for (int i=k;i<=n;i++) res=add(res,f[i]);
	cout<<res;
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值