【XSY3961】决战圣诞树(dfn序线段树,多项式,生成函数)

题面

决战圣诞树

题解

a i a_i ai 表示最终满意指数为 i i i 的方案数,我们考虑求出 a i a_i ai 的生成函数。

那么树上每个点都要维护一个多项式,表示仅对于这个点的 a i a_i ai 的生成函数。

那么题目就可以看成支持以下几个操作:

  1. 给定 v , a v,a v,a,将节点 v v v 的多项式乘上 ( 1 + x a ) (1+x^a) (1+xa)

  2. 给定 v , b , c v,b,c v,b,c,将节点 v v v 的多项式乘上 ( 1 + x a + x b ) (1+x^a+x^b) (1+xa+xb)

  3. 给定 v v v,将与节点 v v v 相邻的节点的多项式都乘上结点 v v v 的多项式。

  4. 给定 u , v u,v u,v,保证 u , v u,v u,v 有连边,将以 u u u 为根时, v v v 子树内所有结点的多项式乘上结点 u u u 的多项式。

  5. 给定 u , v , s u,v,s u,v,s,保证 u , v u,v u,v 有连边,断开这条边,分别求分成的两棵子树中结点的多项式乘积在模 ( x K − 1 ) (x^K-1) (xK1) 意义下的 s s s 次项系数。答案对素数 P P P 取模。

    补充知识:为什么模 ( x K − 1 ) (x^K-1) (xK1) 就是对的?

    我们求出多项式乘积后,根据题目要求,要把 x m x^m xm m ≥ K m\geq K mK)的系数加到 x m   m o d   K x^{m\bmod K} xmmodK 上。而用大除法验证可知模 ( x K − 1 ) (x^K-1) (xK1) 就能实现这个过程。

保证 P   m o d   K = 1 P\bmod K=1 PmodK=1

考虑类似 FFT 的过程维护两个多项式的循环卷积。

我们找到模 P P P 意义下的 K K K 次单位根。具体来说,先找到 P P P 的原根 g g g,然后 g ′ = g P − 1 K g'=g^{\frac{P-1}{K}} g=gKP1 就是满足要求的数。

补充知识:如何找 P P P 的原根 g g g

我们先求出 φ ( P ) \varphi(P) φ(P),然后对其分解质因数 φ ( P ) = p 1 a 1 p 2 a 2 ⋯ p k a k \varphi(P)=p_1^{a_1}p_2^{a_2}\cdots p_k^{a_k} φ(P)=p1a1p2a2pkak

然后我们枚举 g g g,判断 g g g 是否是 P P P 的原根:

  1. 先判断 g φ ( P ) ≡ 1 ( m o d P ) g^{\varphi(P)}\equiv 1\pmod P gφ(P)1(modP),如果不满足则 g g g 不是原根。
  2. 再枚举 i i i,判断 g φ ( P ) p i ≡ 1 ( m o d P ) g^{\frac{\varphi(P)}{p_i}}\equiv 1\pmod P gpiφ(P)1(modP),如果满足则 g g g 不是原根。

然后,如果我们要求 A ( x ) A(x) A(x) B ( x ) B(x) B(x) 的乘积 C ( x ) C(x) C(x) 在模 ( x K − 1 ) (x^K-1) (xK1) 意义下的系数(系数模 P P P),我们就类似 FFT,先将 g ′ 0 , g ′ 1 , g ′ 2 , ⋯   , g ′ K − 1 g'^0,g'^1,g'^2,\cdots,g'^{K-1} g0,g1,g2,,gK1 分别代入 A ( x ) A(x) A(x) B ( x ) B(x) B(x) 求值,得到 A ( x ) A(x) A(x) B ( x ) B(x) B(x) 的点值表达式。然后把它们对应的点值乘起来,得到 C ( x ) C(x) C(x) 的点值表达式。然后再代入 g ′ − 0 , g ′ − 1 , g ′ − 2 , ⋯   , g ′ − ( K − 1 ) g'^{-0},g'^{-1},g'^{-2},\cdots,g'^{-(K-1)} g0,g1,g2,,g(K1) 求值,再除个 K K K 就得到 C ( x ) C(x) C(x) 的系数表达式了。(注意这个过程后得到的 C ( x ) C(x) C(x) 已经模过 ( x K − 1 ) (x^K-1) (xK1) 了)

那我们不妨只维护每个点的多项式的点值表达式,然后询问的时候再转回系数表达式。

这样做的好处是两个多项式相乘只需要 O ( K ) O(K) O(K)

而由于操作 1 和 2 要乘的多项式的项数很少,我们可以直接暴力代入 K K K 个点值。

由于询问的是乘出来的多项式的第 s s s 项的系数,所以点值表达式转系数表达式时不一定要把全部系数都求出来,只需要 O ( K ) O(K) O(K) 求出 x s x^s xs 的系数即可。

而对于操作 3 和 4,我们既需要维护子树信息,又需要维护与一个点直接相连的点的信息。

考虑将 dfs 序和 bfs 序结合起来,我们尝试如下标号方式:一开始先把根标号 1 1 1,然后 dfs。当 dfs 到某一个节点 u u u 时,我们先将 u u u 的儿子按顺序标号,然后再对于每一个儿子进去 dfs。不难发现这种方式既能保证一个点的后代标号连续,又能保证一个点的儿子标号连续。

然后就可以线段树维护多项式(点值)乘积了。

小 trick:

注意到线段树是需要维护区间乘、区间乘积的。那么如果某个长度为 l l l 的区间里面的每一个数都要乘上 a a a 0 ≤ a < P 0\leq a<P 0a<P),这个区间的区间乘积就要乘上 a l a^l al。如果每次都用快速幂的话,单词修改的复杂度就会变成 O ( K log ⁡ 2 n ) O(K\log ^2n) O(Klog2n),所以考虑 O ( 1 ) O(1) O(1) a l a^l al

由于我们具体要求的是 a l   m o d   P a^l \bmod P almodP 的值,而我们又已经求出了 P P P 的原根 g g g。注意到 g 0 , g 1 , ⋯   , g φ ( P ) − 1 g^0,g^1,\cdots,g^{\varphi(P)-1} g0,g1,,gφ(P)1 的值和与 P P P 互质的 φ ( P ) \varphi(P) φ(P) 个值是一一对应的。那么当 P P P 为质数时, g 0 , g 1 , ⋯   , g P − 2 g^0,g^1,\cdots,g^{P-2} g0,g1,,gP2 的值和 1 ∼ P − 1 1\sim P-1 1P1 是一一对应的。

那么不妨设 a = g b a=g^b a=gb a = 0 a=0 a=0 时特判),那么我们要求的是 a l = g b l a^l=g^{bl} al=gbl P P P 的值,又由欧拉定理知 g b l ≡ g b l   m o d   φ ( P ) ≡ g b l   m o d   ( P − 1 ) ( m o d P ) g^{bl}\equiv g^{bl\bmod \varphi(P)}\equiv g^{bl\bmod (P-1)}\pmod P gblgblmodφ(P)gblmod(P1)(modP)

所以我们可以预处理出当 g 0 ∼ P − 2 g^{0\sim P-2} g0P2 的值,同时预处理出 1 ∼ P − 1 1\sim P-1 1P1 对应的是 g g g 的几次方,就可以在线 O ( 1 ) O(1) O(1) a l a^l al 了。

总时间复杂度 O ( ( n + q ) K log ⁡ n + P ) O((n+q)K\log n+P) O((n+q)Klogn+P)

代码比较长也比较难调,幸亏良(凉)心出题人给了个很好的大样例。

代码如下:

#include<bits/stdc++.h>

#define MK 210
#define N 5010
#define P 2000010

using namespace std;

namespace modular
{
	int mod;
	inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
	inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
	inline int mul(int x,int y){return 1ll*x*y%mod;}
}using namespace modular;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

int poww(int a,int b)
{
	int ans=1;
	while(b)
	{
		if(b&1) ans=mul(ans,a);
		a=mul(a,a);
		b>>=1;
	}
	return ans;
}

struct data
{
	int val[MK];
	data(){memset(val,0,sizeof(val));}
}all1,sum[N<<2],lazy[N<<2];

int T,K,invK;
int n,q,datatype;
int g1,g2,p[10],g[MK],powg[P],mp[P];
int cnt,head[N],to[N<<1],nxt[N<<1];
int idx,dfn[N],rk[N],sonl[N],sonr[N];
int fa[N],size[N];
int ans[2];
bool tag[N<<2];

int getphi(int n)
{
	int ans=n,t=sqrt(n);
	for(int i=2;i<=t;i++)
	{
		if(n%i) continue;
		ans=ans/i*(i-1);
		while(!(n%i)) n/=i;
	}
	if(n>1) ans=ans/n*(n-1);
	return ans;
}

void getg()
{
	int phi=getphi(mod);
	int tmp=phi,t=sqrt(phi);
	for(int i=2;i<=t;i++)
	{
		if(!(tmp%i))
		{
			p[++p[0]]=i;
			while(!(tmp%i)) tmp/=i;
		}
	}
	for(g1=2;;g1++)
	{
		if(poww(g1,phi)!=1) continue;
		bool flag=1;
		for(int i=1;i<=p[0];i++)
		{
			if(poww(g1,phi/p[i])==1)
			{
				flag=0;
				break;
			}
		}
		if(flag) break;
	}
}

void init()
{
	invK=poww(K,mod-2);
	for(int i=0;i<K;i++) all1.val[i]=1;
	getg();
	g2=poww(g1,(mod-1)/K);
	g[0]=1;
	for(int i=1;i<K;i++) g[i]=mul(g[i-1],g2);
	powg[0]=1,mp[powg[0]]=0;
	for(int i=1;i<mod-1;i++)
	{
		powg[i]=mul(powg[i-1],g1);
		mp[powg[i]]=i;
	}
}

void adde(int u,int v)
{
	to[++cnt]=v;
	nxt[cnt]=head[u];
	head[u]=cnt;
}

void dfs(int u)
{
	size[u]=1;
	for(int i=head[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v==fa[u]) continue;
		fa[v]=u;
		dfn[v]=++idx,rk[idx]=v;
		if(!sonl[u]) sonl[u]=dfn[v];
		sonr[u]=dfn[v];
	}
	for(int i=head[u];i;i=nxt[i])
	{
		int v=to[i];
		if(v!=fa[u])
		{
			dfs(v);
			size[u]+=size[v];
		}
	}
}

void up(int k)
{
	for(int i=0;i<K;i++)
		sum[k].val[i]=mul(sum[k<<1].val[i],sum[k<<1|1].val[i]);
}

void downn(int k,int len,data now)
{
	for(int i=0;i<K;i++)
	{
		if(!now.val[i])
		{
			sum[k].val[i]=lazy[k].val[i]=0;
			continue;
		}
		int tmp=1ll*mp[now.val[i]]*len%(mod-1);
		sum[k].val[i]=mul(sum[k].val[i],powg[tmp]);
		lazy[k].val[i]=mul(lazy[k].val[i],now.val[i]);
	}
	tag[k]=1;
}

void down(int k,int l,int r,int mid)
{
	if(tag[k])
	{
		downn(k<<1,mid-l+1,lazy[k]),downn(k<<1|1,r-mid,lazy[k]);
		tag[k]=0,lazy[k]=all1;
	}
}

void build(int k,int l,int r)
{
	tag[k]=0,sum[k]=lazy[k]=all1;
	if(l==r) return;
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
}

void update1(int k,int l,int r,int x,data now)
{
	if(l==r)
	{
		for(int i=0;i<K;i++) 
			sum[k].val[i]=mul(sum[k].val[i],now.val[i]);
		return;
	}
	int mid=(l+r)>>1;
	down(k,l,r,mid);
	if(x<=mid) update1(k<<1,l,mid,x,now);
	else update1(k<<1|1,mid+1,r,x,now);
	up(k);
}

void update2(int k,int l,int r,int ql,int qr,data now)
{
	if(ql<=l&&r<=qr)
	{
		downn(k,r-l+1,now);
		return;
	}
	int mid=(l+r)>>1;
	down(k,l,r,mid);
	if(ql<=mid) update2(k<<1,l,mid,ql,qr,now);
	if(qr>mid) update2(k<<1|1,mid+1,r,ql,qr,now);
	up(k);
}

data query1(int k,int l,int r,int x)
{
	if(l==r) return sum[k];
	int mid=(l+r)>>1;
	down(k,l,r,mid);
	if(x<=mid) return query1(k<<1,l,mid,x);
	return query1(k<<1|1,mid+1,r,x);
}

data query2(int k,int l,int r,int ql,int qr)
{
	if(ql<=l&&r<=qr) return sum[k];
	int mid=(l+r)>>1;
	down(k,l,r,mid);
	data ans=all1;
	if(ql<=mid)
	{
		data now=query2(k<<1,l,mid,ql,qr);
		for(int i=0;i<K;i++)
			ans.val[i]=mul(ans.val[i],now.val[i]);
	}
	if(qr>mid)
	{
		data now=query2(k<<1|1,mid+1,r,ql,qr);
		for(int i=0;i<K;i++)
			ans.val[i]=mul(ans.val[i],now.val[i]);
	}
	return ans;
}

int main()
{
	T=read(),K=read(),mod=read();
	init();
	while(T--)
	{
		n=read(),q=read(),datatype=read();
		cnt=idx=0;
		for(int i=1;i<=n;i++) head[i]=sonl[i]=0;
		for(int i=1;i<n;i++)
		{
			int u=read(),v=read();
			adde(u,v),adde(v,u);
		}
		dfn[1]=++idx,rk[idx]=1;
		dfs(1);
		build(1,1,n);
		while(q--)
		{
			char opt[1];
			scanf("%s",opt);
			if(opt[0]=='L')
			{
				int u=read(),a=read();
				data now;
				for(int i=0;i<K;i++)
					now.val[i]=add(1,poww(g[i],a));
				update1(1,1,n,dfn[u],now);
			}
			if(opt[0]=='C')
			{
				int u=read(),a=read(),b=read();
				data now;
				for(int i=0;i<K;i++)
					now.val[i]=add(1,add(poww(g[i],a),poww(g[i],b)));
				update1(1,1,n,dfn[u],now);
			}
			if(opt[0]=='F')
			{
				int u=read();
				data now=query1(1,1,n,dfn[u]);
				if(fa[u]) update1(1,1,n,dfn[fa[u]],now);
				if(sonl[u]) update2(1,1,n,sonl[u],sonr[u],now);
			}
			if(opt[0]=='S')
			{
				int u=read(),v=read();
				data now=query1(1,1,n,dfn[u]);
				if(v==fa[u])
				{
					if(1<=dfn[u]-1) update2(1,1,n,1,dfn[u]-1,now);
					if(sonl[u]&&dfn[u]+1<=sonl[u]-1) update2(1,1,n,dfn[u]+1,sonl[u]-1,now);
					if(sonl[u])
					{
						if(sonl[u]+size[u]-1<=n) update2(1,1,n,sonl[u]+size[u]-1,n,now);
					}
					else 
					{
						if(dfn[u]+1<=n) update2(1,1,n,dfn[u]+1,n,now);
					}
				}
				else
				{
					update1(1,1,n,dfn[v],now);
					if(sonl[v]) update2(1,1,n,sonl[v],sonl[v]+size[v]-2,now);
				}
			}
			if(opt[0]=='Q')
			{
				int u=read(),v=read(),s=read();
				bool rev=0;
				if(v==fa[u]) swap(u,v),rev=1;
				data now1=(1<=dfn[v]-1?query2(1,1,n,1,dfn[v]-1):all1);
				data now2=(sonl[v]&&dfn[v]+1<=sonl[v]-1?query2(1,1,n,dfn[v]+1,sonl[v]-1):all1);
				data now3;
				if(sonl[v]) now3=(sonl[v]+size[v]-1<=n?query2(1,1,n,sonl[v]+size[v]-1,n):all1);
				else now3=(dfn[v]+1<=n?query2(1,1,n,dfn[v]+1,n):all1);
				data now;
				int tmp=1;
				ans[0]=0;
				for(int i=0;i<K;tmp=mul(tmp,g[s?K-s:s]),i++)
				{
					now.val[i]=mul(now1.val[i],mul(now2.val[i],now3.val[i]));
					ans[0]=add(ans[0],mul(now.val[i],tmp));
				}
				ans[0]=mul(ans[0],invK);
				
				now1=query1(1,1,n,dfn[v]);
				now2=(sonl[v]?query2(1,1,n,sonl[v],sonl[v]+size[v]-2):all1);
				tmp=1;
				ans[1]=0;
				for(int i=0;i<K;tmp=mul(tmp,g[s?K-s:s]),i++)
				{
					now.val[i]=mul(now1.val[i],now2.val[i]);
					ans[1]=add(ans[1],mul(now.val[i],tmp));
				}
				ans[1]=mul(ans[1],invK);
				printf("%d %d\n",ans[0^rev],ans[1^rev]);
			}
		}
	}
	return 0;
}
/*
1 119 974849
5 17 31
1 2
1 3
2 4
2 5
Q 1 3 0
Q 1 2 54
L 2 24
Q 1 2 24
C 1 20 30
Q 2 4 54
Q 1 2 54
Q 5 2 54
F 2
Q 1 2 24
Q 1 2 48
Q 1 2 72
S 1 3
Q 2 4 24
Q 2 4 48
Q 2 4 54
Q 2 4 72
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据提供的引用内容,你遇到的问题是在发送HTTP POST请求时收到了403 Forbidden的错误。这个错误通常表示你没有权限访问所请求的资源。 要解决这个问题,你可以采取以下步骤: 1. 首先,确保你的请求URL正确,并且你有权限访问该URL。你可以尝试在浏览器中直接访问该URL,看看是否能够成功访问。 2. 如果你确定URL是正确的,并且你有权限访问,那么可能是你的请求中缺少了必要的身份验证信息。你可以检查你的请求头中是否包含了正确的身份验证信息,比如Token或用户名密码。 3. 另外,你还可以检查服务器端的配置,确保你的请求被正确地处理和授权。你可以查看服务器的日志,以了解更多关于403错误的详细信息。 综上所述,当你收到403 Forbidden错误时,你应该首先检查URL和权限,然后确保请求中包含了正确的身份验证信息。如果问题仍然存在,你可以进一步检查服务器端的配置和日志,以找出问题的根本原因。 #### 引用[.reference_title] - *1* [kubeadm init报错10248...(The HTTP call equal to ‘curl -sSL http://localhost:10248/healthz‘ failed)](https://blog.csdn.net/weixin_45969972/article/details/123529966)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [c/c++使用libcurl库做http客户端及封装(HTTP_GET和HTTP_POST)](https://blog.csdn.net/xsy29000/article/details/103181267)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值