【省选联考 2022 D1T2】填树 题解

Description

传送门

Solution

为方便叙述,令 M = max ⁡ i = 1 n { r i } M=\max_{i=1}^n \{r_i\} M=maxi=1n{ri} p ( u , v , l , r ) p(u,v,l,r) p(u,v,l,r) 表示有多少种给 ( u , v ) (u,v) (u,v) 路径上的点钦定点权的方案数,满足各个 u u u 的点权 均在 [ l u , r u ] [l_u,r_u] [lu,ru] 范围内且最大值不超过 r r r,最小值不小于 l l l。令 q ( u , v , l , r ) q(u,v,l,r) q(u,v,l,r) 表示各个合法方案的点权和之和( p p p 对应第一问, q q q 对应第二问)。再令 p ( l , r ) = ∑ u , v p ( u , v , l , r ) p(l,r)=\sum_{u,v} p(u,v,l,r) p(l,r)=u,vp(u,v,l,r) q ( l , r ) = ∑ u , v q ( u , v , l , r ) q(l,r)=\sum_{u,v} q(u,v,l,r) q(l,r)=u,vq(u,v,l,r)

算法一

首先考虑容斥,不难发现第一问答案为

∑ i = 1 M − K p ( i , i + K ) − ∑ i = 2 M − K p ( i , i + K − 1 ) \sum_{i=1}^{M-K} p(i,i+K)-\sum_{i=2}^{M-K} p(i,i+K-1) i=1MKp(i,i+K)i=2MKp(i,i+K1)

第二问答案同理,把 n n n 换成 q q q 就行了。从而,问题转化为如何对特定的 L , R L,R L,R 求出 p ( L , R ) , q ( L , R ) p(L,R),q(L,R) p(L,R),q(L,R)

Part 1

首先考虑求出 p ( L , R ) p(L,R) p(L,R)

对节点 u u u,令 a u a_u au [ l u , r u ] [l_u,r_u] [lu,ru] [ L , R ] [L,R] [L,R] 的交集的大小,令一条路径的权值为该路径上各个点的 a a a 的乘积,那么 q ( L , R ) q(L,R) q(L,R) 就是各条路径的权值之和。

树上 dp \text{dp} dp 即可,时间复杂度 O ( n M ) O(nM) O(nM)

Part 2

再考虑求出 q ( L , R ) q(L,R) q(L,R)

对节点 u u u,令 b u b_u bu [ l u , r u ] [l_u,r_u] [lu,ru] [ L , R ] [L,R] [L,R] 的交集区间中各数的和, a u a_u au 为交集区间的大小。

考虑 b u b_u bu 会对答案贡献多少次,不难发现这个次数就是,在钦定 a u = 1 a_u=1 au=1 后所有经过 u u u 的路径的权值之和。

于是问题转化为如何对于各个点 u u u 求出上述值。如果做 n n n dp \text{dp} dp 复杂度会退化成 O ( n 2 M ) O(n^2 M) O(n2M),所以我们考虑换根 dp \text{dp} dp

具体来说,我们先对树进行第一遍 dfs,对各个点 u u u 求出 f u , g u f_u,g_u fu,gu,前者表示以 u u u 为根的子树内所有经过 u u u 的路径的权值和, g u g_u gu 表示所有以 u u u 为一端,以某个子树内的点为另一端的路径的权值和。接着,我们再对树进行第二遍 dfs \text{dfs} dfs 以求出 F u F_u Fu,即所有经过 u u u 的路径的权值(在钦定 a u = 1 a_u=1 au=1 的前提下)和。我们可以在每次从父亲 u u u 向儿子 v v v 转移的时候,初始化 g v : = g u − g v a u g_v:=g_u-g_va_u gv:=gugvau,那么 g v g_v gv 的初始值就是外子树的贡献,从而每次大力重新枚举儿子转移就好了。

上述做法常数相对较小。时间复杂度被优化为 O ( n M ) O(nM) O(nM)

算法二

根据 NOI2019 机器人 的经典套路,我们考虑将值域划分为若干段,使得每一段 [ l , r ] [l,r] [l,r] 里面的 ( l ≤ x ≤ r ) p ( x , x + k ) , q ( x , x + k ) (l \le x \le r)p(x,x+k),q(x,x+k) (lxr)p(x,x+k),q(x,x+k) 分别落在一个 n , n + 1 n,n+1 n,n+1 次函数上,那么我们做前缀和之后拉格朗日插值就好了。

注意到,值域被划分成了 O ( 4 n ) O(4n) O(4n) 段,每一段里面要预先做 O ( n ) O(n) O(n) dp \text{dp} dp 以求出前若干个的点值,而每次 dp \text{dp} dp 都是 O ( n ) O(n) O(n) 的,所以时间复杂度就是 O ( n 3 ) O(n^3) O(n3) 的。

这时你可能会有一个疑问:为什么它是正确的呢?下面是证明。

  • 先考虑证明 p p p 至多是 n n n 次函数。不难发现,当 x x x 在范围内移动时,各个点对应的 a a a 值必定是一个一次函数;而路径的权值是各个上面的点的 a a a 值之积,所以 p p p 就是一个 n n n 次函数了。
  • 再考虑证明 q q q 至多是 n + 1 n+1 n+1 次函数。与 p p p 的证明同理,不难发现经过点 u u u 的路径至多经过 n − 1 n-1 n1 个一次函数,而又会在此基础上乘上一个二次函数( b u b_u bu),所以 q q q 就是一个 n + 1 n+1 n+1 次函数了。
  • 最后,明确对任意函数 f ( x ) f(x) f(x) 做变换 f ( x ) : = ∑ 1 ≤ i ≤ x f ( i ) f(x):=\sum_{1 \le i \le x}f(i) f(x):=1ixf(i) 之后,新的 f ( x ) f(x) f(x) 的次数会恰好加 1 1 1。从而, p p p 的前缀和是一个 n + 1 n+1 n+1 次函数, q q q 的前缀和是一个 n + 2 n+2 n+2 次函数,就可以拉格朗日插值了。

其实,如果树的直径较小,多项式的次数也会很小,因此若你被卡常了,你可以加上这个优化。另外注意,拉格朗日插值时的点值是连续的,所以我们可以线性或线性对数地插值,这样就能少一个复杂度瓶颈了。

于是我们解决了省选的 D1T2。

Code

大约 4K 左右,还是挺好写的,就是容易被卡常。

#include <bits/stdc++.h>
#define int long long
#define PA pair<int,int>
#define MP make_pair
#define fi first
#define se second
using namespace std;
const int maxl=205,mod=1e9+7;

int read(){
    int s=0,w=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if (ch=='-')  w=-w;ch=getchar();}
    while (ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+(ch^'0');ch=getchar();}
    return s*w;
}
int n,M,K,cnt,res1,res2,ans1,ans2;
int head[maxl],l[maxl],r[maxl],a[maxl],b[maxl],X[maxl],Y[maxl],jc[maxl],inv[maxl];

struct edge{int nxt,to;}e[maxl<<1];
struct node{int k,n,or_12,opt;};vector<node> ve[maxl];
void add_edge(int u,int v){e[++cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;}
void ge(int &x){x=(x%mod+mod)%mod;}
//math
namespace Math{
	int getsum(int l,int r){
		int sum=(l+r)*(r-l+1);sum/=2,sum%=mod;
		return sum;
	}
	int quick_power(int x,int y){
		int res=1;
		for (;y;y=y>>1,x=(x*x)%mod){
			if (y&1)  res=(res*x)%mod;
		}
		return res;
	}
	int ny(int tmpx){return quick_power(tmpx,mod-2);}
	void init(int n){
		jc[0]=1;
		for (int i=1;i<=n;i++)  jc[i]=(jc[i-1]*i)%mod;
		
		inv[n]=ny(jc[n]);
		for (int i=n-1;i>=0;i--)  inv[i]=(inv[i+1]*(i+1))%mod;
	}
	int solve(int n,int l,int p){
		int res=0,val=1;
		for (int i=1;i<=n;i++)  val=(val*(p-X[i]))%mod;
		for (int i=1;i<=n;i++){
			int tot=(inv[i-1]*inv[n-i])%mod;
			if ((n-i)&1)  tot=mod-tot;
			tot=(tot*val)%mod;
			tot=(tot*Math::ny(p-X[i]))%mod;
			tot=(tot*Y[i])%mod;//1,2,...,i-1,-1,-2,...,i-n
			res=(res+tot)%mod;
		}
		return res;
	}
}
//treework
namespace Treework{
	int f[maxl],g[maxl],F[maxl],A[maxl],B[maxl];
	void dfs(int now,int fath){
		f[now]=g[now]=a[now];
		for (int i=head[now];i;i=e[i].nxt){
			int y=e[i].to;
			if (y==fath)  continue;
			dfs(y,now);
			f[now]=(f[now]+g[now]*g[y])%mod;
			g[now]=(g[now]+g[y]*a[now])%mod;
		}
		res1+=f[now];
	}
	void dfs2(int now,int fath){
		if (!a[now])  F[now]=g[now]=0;
		else{
			F[now]=(++g[now]);
			for (int i=head[now];i;i=e[i].nxt){
				int y=e[i].to;
				if (y==fath)  continue;
				F[now]+=g[now]*g[y],g[now]+=g[y];
				F[now]%=mod,g[now]%=mod;
			}
			res2=(res2+F[now]*b[now])%mod,g[now]=(g[now]*a[now])%mod;
		}
		for (int i=head[now];i;i=e[i].nxt){
			int y=e[i].to;
			if (y==fath)  continue;
			if (a[y])  g[y]=((g[now]-g[y]*a[now])%mod+mod)%mod;
			dfs2(y,now);
		}
	}
	void init(int le,int ri){
		for (int i=1;i<=n;i++){
			int L=l[i],R=r[i];L=max(L,le),R=min(R,ri);
			if (L<=R)  a[i]=R-L+1,b[i]=Math::getsum(L,R);
			else a[i]=b[i]=0;
		}
	}
	void work(int l,int r){
		init(l,r),dfs(1,0);
		g[1]=0,dfs2(1,0),res1%=mod;
	}
	void solve(int l,int r,int k){
		if (r-l<=n+2){
			for (int i=l;i<=r;i++)  work(i,i+k);
			ans1+=res1,ans2+=res2;
		}
		else{
			for (int i=l;i<=l+n+2;i++){
				work(i,i+k);
				A[i-l+1]=res1,B[i-l+1]=res2;
			}
			for (int i=l;i<=l+n+1;i++)  X[i-l+1]=i,Y[i-l+1]=A[i-l+1];
			ans1+=Math::solve(n+2,l,r);
			
			for (int i=l;i<=l+n+2;i++)  X[i-l+1]=i,Y[i-l+1]=B[i-l+1];
			ans2+=Math::solve(n+3,l,r);
		}
		res1=res2=0;
	}
}
//mainwork
namespace ducati{
	int ANS1,ANS2;vector<int> ve;
	void push(int x,int lim){if(x>0&&x<=lim)ve.push_back(x);}
	void get_all_in(){
		n=read(),K=read();
		for (int i=1;i<=n;i++)  l[i]=read(),r[i]=read(),M=max(M,r[i]);
		for (int i=1;i<n;i++){
			int u=read(),v=read();
			add_edge(u,v),add_edge(v,u);
		}
	}
	void init_segment(int k){
		ve.clear(),ve.push_back(1),ve.push_back(M-k+1);
		for (int i=1;i<=n;i++)
		  push(l[i]-k,M-k),push(l[i],M-k),push(r[i]-k+1,M-k),push(r[i]+1,M-k);
		sort(ve.begin(),ve.end());
	}
	void work(int k){
		init_segment(k);
		for (int i=1;i<ve.size();i++){
			int l=ve[i-1],r=ve[i]-1;
			if (k==K-1)  l=max(l,2ll),r=min(r,M-k-1);
			if (l<=r)  Treework::solve(l,r,k);
		}
	}
	void solve(){
		get_all_in(),Math::init(n+3);
		ans1=ans2=0,work(K),ANS1+=ans1,ANS2+=ans2;
		ans1=ans2=0,work(K-1),ANS1-=ans1,ANS2-=ans2;
		ge(ANS1),ge(ANS2),cout<<ANS1<<endl<<ANS2<<endl;
	}
}
signed main(){ducati::solve();return 0;}

花絮

考场上 A \text{A} A 调了 3 3 3 个半小时屁都没调出来。

然后想出 B 的正解根本就来不及写,最后写了 B,C 的暴力就走人了。

然后 Day 1 就只有 60 60 60 多分,Day 2 拼命翻却只翻了一半。

于是,这就是我的傻逼省选场外赛

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值