【生成函数+拉格朗日插值+整体DP+线段树合并】LGP4365 [九省联考2018]秘密袭击coat

【题目】
原题地址
给定一棵 N N N个节点的树,点权 1 ∼ W 1 \sim W 1W,求树的每一个连通块的第 K K K大点权之和。

【解题思路】
以下均来自这里

首先可以转化一下题目。

A n s = ∑ S ∈ T k t h    o f    S = ∑ i = 1 W i × ∑ S ∈ T [ k t h   o f   S = = i ] = ∑ i = 1 W ∑ S ∈ T [ k t h   o f   S ⩾ i ] = ∑ i = 1 W ∑ S ∈ T [ c n t [ i ] ⩾ k ] \begin{aligned} Ans &= \sum_{S \in T} k_{th} \ \ of \ \ S \\ &= \sum_{i=1}^{W} i \times \sum_{S \in T} [k_{th}\ of \ S==i]\\ &=\sum_{i=1}^{W} \sum_{S \in T} {[k_{th} \ of \ S \geqslant i]}\\ &=\sum_{i=1}^{W} \sum_{S \in T} {[cnt[i] \geqslant k]} \end{aligned} Ans=STkth  of  S=i=1Wi×ST[kth of S==i]=i=1WST[kth of Si]=i=1WST[cnt[i]k]
现在问题转化为了枚举一个权值 v v v,求 v v v的权值出现次数超过 k k k的连通块个数。
f [ i ] [ j ] [ k ] f[i][j][k] f[i][j][k]表示以 i i i为根的子树,大于等于 j j j的权值出现次数大于等于 k k k的方案数。
f [ i ] [ j ] [ k ] = ∏ v ∈ s o n [ i ] f [ v ] [ j ] [ k ′ ]      ( d [ i ] &lt; j , ∑ k ′ = k ) f [ i ] [ j ] [ k ] = ∏ v ∈ s o n [ i ] f [ v ] [ j ] [ k ′ ]      ( d [ i ] ⩾ j , ∑ k ′ = k − 1 ) \begin{aligned} f[i][j][k] &amp;= \prod_{v \in son[i]} f[v][j][k&#x27;] \ \ \ \ (d[i]&lt;j,\sum k&#x27;=k)\\ f[i][j][k] &amp;= \prod_{v \in son[i]} f[v][j][k&#x27;] \ \ \ \ (d[i] \geqslant j,\sum k&#x27;=k-1) \end{aligned} f[i][j][k]f[i][j][k]=vson[i]f[v][j][k]    (d[i]<j,k=k)=vson[i]f[v][j][k]    (d[i]j,k=k1)
答案就是 ∑ k ’ = 1 k ∑ j = 1 W ∑ i = 1 N f [ i ] [ j ] [ k ’ ] \sum\limits_{k’=1}^{k}\sum\limits_{j=1}^{W}\sum\limits_{i=1}^{N} f[i][j][k’] k=1kj=1Wi=1Nf[i][j][k]

这个复杂度显然还不够优秀。
观察到转移实际上相当于一个背包,我们可以考虑生成函数直接算出系数。

F [ i ] [ j ] F[i][j] F[i][j]表示以 i i i为根的子树中,权值大于等于 j j j的权值的生成函数。

F [ i ] [ j ] = ∑ k = 0 n f [ i ] [ j ] [ k ] × x k F[i][j]=\sum\limits_{k=0}^{n} f[i][j][k] \times x^k F[i][j]=k=0nf[i][j][k]×xk,这是一个 N N N次多项式。

但是最后我们要求的是整棵树的所有 F [ i ] [ j ] F[i][j] F[i][j]之和,所以我们不妨再设一个 G [ i ] [ j ] G[i][j] G[i][j]

G [ i ] [ j ] = ∑ x ∈ s u b t r e e ( i ) F [ x ] [ j ] G[i][j]=\sum\limits_{x \in subtree(i)}F[x][j] G[i][j]=xsubtree(i)F[x][j]

F [ i ] [ j ] F[i][j] F[i][j]在转移时是多项式卷积,还是很慢, G [ i ] [ j ] G[i][j] G[i][j]在转移时只要维护一下就行了。

所以我们考虑将它转换为$N+1$1个点值,这样的话转移时就是普通乘法了。

我们就令 x = 1 ∼ N + 1 x=1 \sim N+1 x=1N+1,然后将所有 G [ i ] [ j ] G[i][j] G[i][j] x x x时的值都求出来,最后进行拉格朗日插值法将原始的多项式差出来就行了,可具体怎么实现呢?

我们首先在最外层枚举 x ∈ [ 1 , N + 1 ] x \in [1,N+1] x[1,N+1],然后每次进行一次 D F S DFS DFS,但具体如何进行转移呢?

我们不难发现, F [ i ] [ j ] ← F [ s o n [ i ] ] [ j ] F[i][j] \leftarrow F[son[i]][j] F[i][j]F[son[i]][j]转移过程中其实就是 [ j ] [j] [j]的对应位置相乘。

所以我们可以使用整体 D P DP DP的思想在每个点上都维护一颗线段树,然后在转移时进行线段树合并就可以了。

具体合并方法如下:

初始化: F [ i ] [ j ] = x      ( d [ i ] ⩾ j ) F[i][j] = x \ \ \ \ (d[i] \geqslant j) F[i][j]=x    (d[i]j)

F [ i ] [ j ] = 1      ( d [ i ] &lt; j ) F[i][j] = 1 \ \ \ \ (d[i] &lt; j) F[i][j]=1    (d[i]<j)

转移时: F [ i ] [ j ] × = ( F [ s o n [ i ] ] [ j ] + 1 ) , G [ i ] [ j ] + = G [ s o n [ i ] ] [ j ] F[i][j] \times =(F[son[i]][j]+1) , G[i][j]+=G[son[i]][j] F[i][j]×=(F[son[i]][j]+1),G[i][j]+=G[son[i]][j]

最后, G [ i ] [ j ] + = F [ i ] [ j ] G[i][j]+=F[i][j] G[i][j]+=F[i][j]

我们可以将 F [ s o n [ i ] ] [ j ] + 1 F[son[i]][j]+1 F[son[i]][j]+1的操作放在 D F S DFS DFS s o n [ i ] son[i] son[i]后进行。

那么线段树到底应该怎么写?

我们设变换 ( a , b , c , d ) (a,b,c,d) (a,b,c,d)可以使 ( f , g ) (f,g) (f,g)变换为 ( a × f + b , c × f + d + g ) (a \times f+b,c \times f+d+g) (a×f+b,c×f+d+g)

然后维护它即可。

【参考代码】

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N=2005,M=N*50,mod=64123;
int n,K,W,tot;
int d[N],head[N],inv[N],yc[N],c[N],g[N],f[N];

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}

inline void up(int &x,int y){x+=y;if(x>=mod)x-=mod;}
inline int upm(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline int mul(int x,int y){return (LL)x*y%mod;}
int qpow(int x,int y){int ret=1;for(;y;y>>=1,x=(LL)x*x%mod)if(y&1)ret=(LL)ret*x%mod;return ret;}

struct Tway{int v,nex;}e[N<<1];
void add(int u,int v)
{
	e[++tot]=(Tway){v,head[u]};head[u]=tot;
	e[++tot]=(Tway){u,head[v]};head[v]=tot;
}

struct data
{
	int a,b,c,d;
	data():a(1),b(0),c(0),d(0){}
	data(int a,int b,int c,int d):a(a),b(b),c(c),d(d){}	
	inline friend data operator * (const data&x,const data&y)
	{
		return data(mul(x.a,y.a),upm(mul(y.a,x.b),y.b),
			        upm(mul(y.c,x.a),x.c),upm(mul(y.c,x.b),upm(x.d,y.d)));
	}
};

struct Segment
{
	data tar[M];
	int top,sz;
	int str[M],rt[N],lc[M],rc[M];

	inline int newnode()
	{
		int x=top?str[top--]:++sz;
		tar[x]=data();lc[x]=rc[x]=0;
		return x;
	}
	
	inline void dele(int &x)
	{
		if(!x) return;
		dele(lc[x]);dele(rc[x]);
		str[++top]=x;x=0;
	}

	inline void pushdown(int x)
	{
		if(!lc[x]) lc[x]=newnode();
		if(!rc[x]) rc[x]=newnode();
		tar[lc[x]]=tar[lc[x]]*tar[x];
		tar[rc[x]]=tar[rc[x]]*tar[x];
		tar[x]=data();
	}

	inline void update(int &x,int l,int r,int L,int R,data v)
	{
		if(!x) x=newnode();
		if(L<=l && r<=R) {tar[x]=tar[x]*v;return;}
		pushdown(x);
		int mid=(l+r)>>1;
		if(L<=mid) update(lc[x],l,mid,L,R,v);
		if(R>mid) update(rc[x],mid+1,r,L,R,v);
	}

	void merge(int &x,int &y)
	{
		if(!x) swap(x,y); if(!y) return;
		if(!lc[x] && !rc[x]) swap(x,y);
		if(!lc[y] && !rc[y]) 
		{
			tar[x].a=mul(tar[x].a,tar[y].b);
			tar[x].b=mul(tar[x].b,tar[y].b);
			tar[x].d=upm(tar[x].d,tar[y].d);
			return;
		}
		pushdown(x);pushdown(y);
		merge(lc[x],lc[y]);merge(rc[x],rc[y]);
	}

	void init(int x,int fa,int x0)
	{
		update(rt[x],1,W,1,W,data(0,1,0,0));
		for(int i=head[x];i;i=e[i].nex)
		{
			int v=e[i].v;
			if(v==fa) continue;
			init(v,x,x0);merge(rt[x],rt[v]);dele(rt[v]);
		}
		if(d[x]) update(rt[x],1,W,1,d[x],data(x0,0,0,0));
		update(rt[x],1,W,1,W,data(1,0,1,0));
		update(rt[x],1,W,1,W,data(1,1,0,0));
	}

	void getval(int x,int l,int r,int p)
	{
		if(l==r){up(yc[p],tar[x].d);return;}
		pushdown(x);
		int mid=(l+r)>>1;
		getval(lc[x],l,mid,p);getval(rc[x],mid+1,r,p);
	}

	void div(int *a,int *b,int x0)
	{
		for(int i=0;i<=n+1;++i) c[i]=a[i];
		for(int i=n+1;i;--i)
			b[i-1]=c[i],up(c[i-1],mul(c[i],x0)),c[i]=0;
	}
}tr;

int calc()
{
	for(int i=1;i<=n+1;++i) inv[i]=qpow(i,mod-2); g[0]=1;
	for(int i=1;i<=n+1;++i) for(int j=n+1;~j;--j)
	{
		g[j]=mul(g[j],mod-i);
		if(j) up(g[j],g[j-1]);
	}

	int ans=0;
	for(int i=1;i<=n+1;++i)
	{
		tr.div(g,f,i);int res=0;
		for(int j=K;j<=n;++j) up(res,f[j]);
		for(int j=1;j<=n+1;++j) if(i^j)
			res=(i>j?mul(res,inv[i-j]):mul(res,mod-inv[j-i]));
		res=mul(res,yc[i]);up(ans,res);
	}
	return ans;
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("LGP4365.in","r",stdin);
	freopen("LGP4365.out","w",stdout);
#endif
	n=read();K=read();W=read();
	for(int i=1;i<=n;++i) d[i]=read();
	for(int i=1;i<n;++i) add(read(),read());
	for(int i=1;i<=n+1;++i)
		tr.init(1,0,i),tr.getval(tr.rt[1],1,W,i),tr.dele(tr.rt[1]);
	printf("%d\n",calc());
	return 0;
}

【总结】
说无可说。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值