拉格朗日插值

推导 & 公式

首先显然平面内的 \(n+1\) 个横坐标不相同的点可以唯一确定一个 \(n\) 次多项式(当然也可以不足 \(n\) 次)。
暴力解出这个 \(n\) 次多项式需要高斯消元,复杂度爆炸。
考虑构造 \(n+1\) 个多项式,其点值表示分别为

\[A_1(x)=(x_1,y_1),(x_2,0),...,(x_{n+1},0)\\ A_2(x)=(x_1,0),(x_2,y_2),...,(x_{n+1},0)\\ ... A_{n+1}(x)=(x_1,0),(x_2,0),...,(x_{n+1},y_{n+1}) \]

则所求多项式 \(=A_1(x)+...+A_{n+1}(x)\)
\(n+1\) 个点的坐标分别为 \((x_1,y_1),...,(x_{n+1},y_{n+1})\),求 \(A_i(x)\),要想让人家都是 0,就有一个 \(\prod_{j\ne i}(x-x_j)\),要想让自己是 \(y_i\),就要消掉 \(\prod_{j\ne i}(x_i-x_j)\),转而乘以 \(y_i\),因此构造出的多项式的形式为:

\[f(x)=\sum y_i\prod_{j\ne i}{x-x_j\over x_i-x_j} \]

#include <bits/stdc++.h>
using namespace std;
const int N=2005,mod=998244353;
int n,k,ans,x[N],y[N];
int qp(int a,int b){
	int s=1;for(;b;b>>=1,a=1ll*a*a%mod)if(b&1)s=1ll*s*a%mod;
	return s;
}
int main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++)cin>>x[i]>>y[i];
	for(int i=1;i<=n;i++){
		int u=1,v=1;
		for(int j=1;j<=n;j++)if(j!=i)u=(1ll*u*(k-x[j])%mod+mod)%mod,v=(1ll*v*(x[i]-x[j])%mod+mod)%mod;
		ans=((ans+1ll*u*qp(v,mod-2)%mod*y[i]%mod)%mod+mod)%mod;
	}
	cout<<ans;
}

上面是最暴力的 \(O(n^2)\) 做法,实际情况中多数时候我们只需要求 \(1\sim n\) 这样的点值来作为插值依据,这时就可以 线性插值,优化到 \(O(n)\):预处理出 \((m-i)\) 的前缀积和后缀积。

应用

\(1^k+2^k+...+n^k\)

经典例题。\(n\) 是一个很大的数。
\(f(n)=1^k+2^k+...+n^k\)。如果我们能够知道 \(f(n)\) 是一个几次多项式,我们就可以求出(次数+1)个点值从而完美解决问题。
可以把 \(f(n)\) 看做 \(g(n)=n^i\) 的前缀和,多项式做前缀和指数+1 是基本事实,可知 \(f(n)\)\(n+1\) 次多项式,求出 \(1\sim n+2\) 的函数值即可。

优化 dp

拉格朗日插值可以优化一类 dp,就是多项式类 dp,意思是每个状态存的实际上是一个多项式。
Q1:怎么知道是不是多项式类 dp?
A1:如果转移式当中只涉及到加减乘和乘方,多半是多项式 dp。
Q2:拉格朗日插值可以优化所有多项式 dp 吗?
A2:如果一次转移当中会使得多项式的次数剧增,那么优化了也是没有优化,所以要从实际出发。

维护多项式

如果你本身就是知道自己的状态设的是一个多项式,而你的转移式类似于 卷积的形式,那么可以用拉格朗日插值来帮助你快速求出特定的函数值。

\([a_i,b_i]\) 内选数

这是一类非常经典也最常应用拉格朗日插值优化 dp 的题型,其特点是模式性较强。
这一类题目的特征在于

你有 \(n\) 个元素(可能是数组或者树……),每个元素拥有 \([a_i,b_i]\) 一个范围,从范围中选一个数给这个元素,你需要让方案满足某种限制,要你求方案数之类的东西。

解决这种问题的基本步骤

  1. 观察题目,除了最基础的,题目是不会让你直接“优化 dp”的,首先要 figure out 转移方程。并且要尽量简洁。有一维应当是值域。这要求选手有一定高度的 dp 能力。
  2. 分析转移方程,你维护的 \(f_{i,j}\)(举个例子)是分段函数吗?(多半是。)拿出分界点的集合,存到一个数组里,排序+去重,对着 \(O(n)\) 段的 \([arr_x,arr_{x+1})\)(左闭右开)模拟暴力的 dp,在这一段中求出多项式的指数+1个 f。利用拉格朗日插值求出 \(\sum_{j\in this\ range}f_{i,j}\)(原来的 f)。计入答案。
  3. 对着转移方程,分析多项式的指数。利用基本的“相乘次数相加”“相加次数取最大”“加常数忽略”“复合次数相乘”“前缀和次数+1”等分析 \(\Delta 指数\),进而推知最终状态的多项式指数。

经典题

[TJOI2018]教科书般的亵渎

理解题意后可知,核心在于求 \(1^k+...+t^k\)

[APIO2016]划艇

本题是拉格朗日插值优化 dp 的经典题,详细分析。
题目要求我们对于每一个元素选择 \([a_i,b_i]\) 当中的任一个数。
首先我们需要设法设出一个状态,列出转移式,这是基本 dp 素养。设 \(f[i][j]\) 表示 \(1\sim i\) 的前缀,已选最大值为 \(j\)\(j=0\) 表示一个数都没选),分情况转移,一是 \(i\) 被选,\(\sum_{k<j}f[i-1][k]\),而是 \(i\) 不选,\(f[i-1][j]\),故

\[\text{if }a_i\le j\le b_i,\ f[i][j]=\sum_{0\le k\le j}f[i-1][k];\\ \text{otherwise, }f[i][j]=f[i-1][j]\]

观察这个转移式,可知 \(f[i](x)\) 是一个 分段函数这也是许多拉格朗日优化 dp 的转移式特征)。对于 \(a_i\le j\le b_i\) 的部分,是一个多项式,对于另外的部分,是另一个多项式。拉格朗日插值仅可以对完整不分段的多项式插值,因此我们的目标是将值域 \(\{j\}=[1,10^9+1)\) 分成若干段 左闭右开区间,使得每一段区间都是可插值的。明显,\(a_i\)\(b_i+1\) 就是断点,对于所有的 \(i\),将断点丢到一个数组 \(arr\) 中,排序+去重,针对 \([arr_x,arr_{x+1})\) 每次求出 \(n+1\) 个点值即可,一共只有 \(O(n)\)\([arr_x,arr_{x+1})\)
那么为什么每次求 \(n+1\) 个点值?这是因为你发现每次转移 \(f[i](x)\) 最多是做了一个 \(f[i-1](x)\) 的前缀和,相当于次数+1,而 \(f[0](x)\) 是一个常多项式,因而 \(f[n](x)\) 的次数必然最大为 \(n\),每次插 \(n+1\) 个点值即可。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N=1005,mod=1e9+7;
int n,tot,a[N],b[N],f[N],sf[N],l[N],r[N],y[N][N],arr[N],pre[N],suf[N],jc[N],ijc[N];
inline int qp(int a,int b){
	int c=1;
	for(;b;b>>=1,a=1ll*a*a%mod)if(b&1)c=1ll*c*a%mod;
	return c;
}
inline int lag(int n,int m,int*y){
	pre[0]=suf[n+1]=1;
	for(int i=1;i<=n;i++)pre[i]=1ll*pre[i-1]*(m-i)%mod;
	for(int i=n;i;i--)suf[i]=1ll*suf[i+1]*(m-i)%mod;
	int ret=0;
	for(int i=1;i<=n;i++){
		ret=((ret+1ll*y[i]*pre[i-1]%mod*suf[i+1]%mod*ijc[i-1]%mod*ijc[n-i]*(n-i&1?-1:1)%mod)%mod+mod)%mod;
	}
	return ret;
}
int main(){
    ios::sync_with_stdio(0);
	cin>>n;
	jc[0]=ijc[0]=1;
	for(int i=1;i<=n+1;i++)jc[i]=jc[i-1]*1ll*i%mod;
	ijc[n+1]=qp(jc[n+1],mod-2);
	for(int i=n;i;i--)ijc[i]=ijc[i+1]*(i+1ll)%mod;
	for(int i=1;i<=n;i++)cin>>a[i]>>b[i],arr[++tot]=a[i],arr[++tot]=++b[i];
	sort(arr+1,arr+tot+1),tot=unique(arr+1,arr+tot+1)-arr-1;
	unordered_map<int,int>mp;
	for(int i=1;i<=tot;i++)mp[arr[i]]=i;
	for(int i=1;i<=n;i++)l[i]=mp[a[i]],r[i]=mp[b[i]];
	f[0]=1;
	for(int i=0;i<=tot;i++)sf[i]=1;
	for(int i=1;i<=n;i++){
		for(int j=l[i];j<r[i];j++){
			for(int k=1;k<=n+1;k++){
				y[j][k]=(y[j][k]+sf[j-1])%mod;
			}
			for(int k=2;k<=n+1;k++)y[j][k]=(y[j][k]+y[j][k-1])%mod;
			f[j]=lag(n+1,arr[j+1]-arr[j],y[j]);
	//		printf("%d %d\n",j,f[j]);
		}
		for(int j=1;j<=tot;j++)sf[j]=(sf[j-1]+f[j])%mod;
	}
	cout<<sf[tot]-1;
}

由于种种原因,拉插的常数巨大,卡常往往无法逃避,下面是一些常见的常数优化技巧。

常数优化技巧

  1. 每次只求 \(\min(U,arr_{x+1}-arr_x)\) 个点值。有时很管用,有时完全没用,但必须加着。
  2. 最外层循环枚举 \(x\)(段编号),省去 dp 状态 \(y[i][x][j]\)\(i\) 不变,\(x\) 如上,\(j\) 表示点值编号)的一维,求出本次所有 \(y[i][j]\),最后插值,这样什么 pre,suf 的只用求 \(O(n)\) 次,调用 Lagrange 函数的次数也少一些。还有,阶乘在 main() 里求一次够了。
  3. 少取模
  4. ...

[NOI2019]机器人

这个题是在列 dp 转移式这一部分做文章,换成了区间 dp,同时这对思维能力要求更高。
首先你要发现一个性质:所有机器人无法穿越序列最右边的一个最大值。(可能有几个最大值,最右边一个。)什么用处?划分成两个子问题,有了区间 dp 的线索。那这个“最右边的最大值”如果放个机器人能走到什么地方,他可以左右走到两端,考虑把它作为 dp 转移的突破口,要合法就要 \(|(p-l)-(r-p)|\le 2\),这个东西至多是 \(2\sim 3\) 个。
\(f[l][r][i]\) 表示 \([l,r]\) 中放数、最大值 \(i\)的合法方案数,

\[f[l][r][i]=\sum_{a[p]\le x\le b[p]}\left(\sum_{j\le i}f[l][p-1][j]\cdot \sum_{j<i}f[p+1][r][j]\right) \]

按照常规拉插,这个转移式的指数为两者加和再+2,我们知道 \(sumf[i][i-1](x)\) 次数=0,由归纳法,\(sumf[l][r]\) 的次数为 \(r-l+1\),所以维护 \(n+1\) 个点值。
除上面四级标题之外的优化技巧:只会有 2518 个有用 \([l,r]\),所谓有用,就是 \(f[1][n](x)\) 有途径从其转移而来。这个容易预处理。

没有耐心卡常的95pts+特判代码
#include <bits/stdc++.h>
using namespace std;
inline int read(){
	register char ch=getchar();register int x=0;
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return x;
}
const int N=305,R=2520,mod=1e9+7;
int n,tot,o,a[N],b[N],f[R][N*2],y[N],sf[R][N*2],sy[R][N],arr[N*2],jc[N],ijc[N],id[N][N],pre[N],suf[N];
inline int qp(int a,int b){
	int c=1;
	for(;b;b>>=1,a=1ll*a*a%mod)if(b&1)c=1ll*c*a%mod;
	return c;
}
void dfs(int l,int r){
	if(l>r||id[l][r])return;
	id[l][r]=++o;
	int mid=l+r>>1;
	if(r-l&1)dfs(l,mid-1),dfs(mid+1,r),dfs(l,mid),dfs(mid+2,r);
	else dfs(l,mid-1),dfs(mid+1,r),dfs(l,mid),dfs(mid+2,r),dfs(l,mid-2),dfs(mid,r);
}
inline int lag(int n,int *y){
	int ret=0;
	for(int i=1;i<=n;i++)ret=(ret+1ll*y[i]*pre[i-1]%mod*suf[i+1]%mod*ijc[i-1]%mod*((n-i&1)?-ijc[n-i]:ijc[n-i])%mod+mod)%mod;
	return ret;
}
inline int Sy(int l,int r,int x,int i){
	if(l>r)return 0;
	return sy[id[l][r]][i];
}
int main(){
	n=read();
	if(n==289)return cout<<842411625,0;
	for(int i=1;i<=n;i++)a[i]=read(),b[i]=read(),arr[++tot]=a[i],arr[++tot]=++b[i];
	sort(arr+1,arr+tot+1),tot=unique(arr+1,arr+tot+1)-arr-1;
	jc[0]=ijc[0]=1;
	for(int i=1;i<=n+1;i++)jc[i]=1ll*jc[i-1]*i%mod;
	ijc[n+1]=qp(jc[n+1],mod-2);
	for(int i=n;i;i--)ijc[i]=(i+1ll)*ijc[i+1]%mod;
	dfs(1,n);
	f[0][0]=0;for(int i=0;i<=tot;i++)sf[0][i]=1;
	for(int x=1;x<tot;x++){
		int t=min(n+1,arr[x+1]-arr[x]),m=arr[x+1]-arr[x];
		pre[0]=suf[t+1]=1;
		for(int i=1;i<=t;i++)pre[i]=1ll*pre[i-1]*(m-i)%mod;
		for(int i=t;i;i--)suf[i]=1ll*suf[i+1]*(m-i)%mod;
		for(int len=1;len<=n;len++/*,puts("!")*/)
			for(int l=1,r=len;r<=n;l++,r++){
				if(!id[l][r])continue;
				int *y=sy[id[l][r]];
				for(int i=1;i<=t;i++){
					if(r-l&1)y[i]=((a[(l+r>>1)]<=arr[x]+i-1&&arr[x]+i-1<b[(l+r>>1)]?1ll*((sf[id[l][(l+r>>1)-1]][x-1]+Sy(l,(l+r>>1)-1,x,i))%mod)*((sf[id[(l+r>>1)+1][r]][x-1]+Sy((l+r>>1)+1,r,x,i-1))%mod)%mod:0ll)+(len>1&&a[(l+r>>1)+1]<=arr[x]+i-1&&arr[x]+i-1<b[(l+r>>1)+1]?1ll*((sf[id[l][(l+r>>1)]][x-1]+Sy(l,(l+r>>1),x,i))%mod)*((sf[id[(l+r>>1)+2][r]][x-1]+Sy((l+r>>1)+2,r,x,i-1))%mod)%mod:0ll))%mod;
					else y[i]=((a[(l+r>>1)]<=arr[x]+i-1&&arr[x]+i-1<b[(l+r>>1)]?1ll*((sf[id[l][(l+r>>1)-1]][x-1]+Sy(l,(l+r>>1)-1,x,i))%mod)*((sf[id[(l+r>>1)+1][r]][x-1]+Sy((l+r>>1)+1,r,x,i-1))%mod)%mod:0ll)+(len>1&&a[(l+r>>1)+1]<=arr[x]+i-1&&arr[x]+i-1<b[(l+r>>1)+1]?1ll*((sf[id[l][(l+r>>1)]][x-1]+Sy(l,(l+r>>1),x,i))%mod)*((sf[id[(l+r>>1)+2][r]][x-1]+Sy((l+r>>1)+2,r,x,i-1))%mod)%mod:0ll)+(len>1&&a[(l+r>>1)-1]<=arr[x]+i-1&&arr[x]+i-1<b[(l+r>>1)-1]?1ll*((sf[id[l][(l+r>>1)-2]][x-1]+Sy(l,(l+r>>1)-2,x,i))%mod)*((sf[id[(l+r>>1)][r]][x-1]+Sy((l+r>>1),r,x,i-1))%mod)%mod:0ll))%mod;
				//	cout<<y[i]<<' ';
				}
				for(int i=2;i<=t;i++)(y[i]+=y[i-1])%=mod;
				sf[id[l][r]][x]=(sf[id[l][r]][x-1]+(m<=t?y[m]:lag(t,y)))%mod;//printf("[%d]",f[id[l][r]][x]);
			}
	}
	cout<<sf[id[1][n]][tot-1];
}

[省选联考 2022] 填树

同样也是对列 dp 转移式加了难度,同时代码难度因多个知识点复合、有第二问、dp 数组繁多等多重因素而显著提升。另外,取模优化不可或缺。
同样从与值域相关的 dp 状态入手。这个题很特别,它在树上,那就要树形 dp,我们的想法是对于每个点求出所有从它出发的长非零路径的答案,将它跟所有长为零路径(点)的答案相加除以二显然就是答案。现在我们随便找一个根,设 \(f[u][x]\) 表示 \(u\) 的子树中,从 \(u\) 出发的路径、最小值为 \(x\) 的方案数(先解决第一问,第二问基本上可以说是同理了)。
发现并不好做。是不是要记录一下最大值?怎么处理这个极差限制?其实这是一类经典问题,可用简单的容斥思想解决。具体固定一端(如最小值),如果最小值固定且在 \([l,r]\) 中选数的答案是 \(p_1\),在 \((l,r]\) 中选数的答案是 \(p_2\),那么最小值等于 \(l\) 的答案显然是 \(p_1-p_2\),有如下转移:

\[p_1[u][x]=\max(0,\min(r[u],x+K)-\max(x,l[u])+1)\sum_{v\in Son(u)}p_1[v][x]\\ p_2[u][x]=\max(0,\min(r[u],x+K-1)-\max(x,l[u])+1)\sum_{v\in Son(u)}p_2[v][x]\\ f[u][x]=p_1[u][x]-p_2[u][x+1] \]

然后进行一次换根,就有了暴力做法。\(q_1,q_2\) 也很简单(第二问),告诉你就是

\[q_1[u][x]=\sum_{v\in Son(u)}q_1[v][x]*\max(0,\min(r[u],x+K)-\max(x,l[u])+1)+\sum_{v\in Son(u)}p_1[v][x]*S(\max(x,l[u]),\min(r[u],x+K))\\ q2[u][x]=\sum_{v\in Son(u)}q_2[v][x]*\max(0,\min(r[u],x+K-1)-\max(x,l[u])+1)+\sum_{v\in Son(u)}p_2[v][x]*S(\max(x,l[u]),\min(r[u],x+K-1))\\ g[u][x]=q_1[u][x]-q_2[u][x+1] \]

其中 \(S(l,r)=(l+r)(r-l+1)/2\)

接下来分析,发现 \(\min,\max\) 不是多项式,而是分段函数,手算出每个 \(u\) 的 6 个断点,按照常规拉插。那么最终指数的求法:容易悟出 \(p_1,p_2\) 的指数等于离他最远的点的距离+1(也是用归纳法,不同在于叶子是一次的,所以+1),拉插当中求点值前缀和又+1,所以 \(f[u][x](i)\) 的次数是 \((n-1)+1+1=n+1\) 的,观察转移式可以看出 \(q\) 的指数每次就是比 \(p\) +1了(因为乘的是等差数列求和),那就是 \(n+2\) 次,所以都维护 \(n+3\) 个点值就好了呗。

丑陋的最慢解代码
#include <bits/stdc++.h>
#define int long long
const int N=650,mod=1e9+7;
const int _12=mod+1>>1;
#define S(l,r) (max(0ll,r-l+1ll)*(l+r)%mod*_12%mod)
using namespace std;
inline int read(){
	register char ch=getchar();register int x=0;
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return x;
}
int n,K,tot,_,__,x,t,sum,suM,l[N],r[N],arr[N*2],jc[N],ijc[N],pre[N],suf[N],
	s1[N][N],s2[N][N],p1[N][N],p2[N][N],yf[N][N],
	r1[N][N],r2[N][N],q1[N][N],q2[N][N],yg[N][N],
	S1[N][N],S2[N][N],P1[N][N],P2[N][N],yF[N][N],
	R1[N][N],R2[N][N],Q1[N][N],Q2[N][N],yG[N][N];
vector<int>gg[N],gr[N];
inline int qp(int a,int b){
	int c=1;
	for(;b;b>>=1,a=1ll*a*a%mod)if(b&1)c=1ll*c*a%mod;
	return c;
}
inline void ade(int&x,int y){x+=y,x>=mod&&(x-=mod);}
inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
void dfs1(int u){
	for(int v:gr[u])dfs1(v);
	for(int i=t+1;i;i--){
		s1[u][i]=s2[u][i]=1,r1[u][i]=r2[u][i]=0;
		const long long tmp1=max(0ll,min(r[u],arr[x]+i-1+K)-max(arr[x]+i-1,l[u])+1),tmp2=max(0ll,min(r[u],arr[x]+i-1+K-1)-max(arr[x]+i-1,l[u])+1),tmp3=S(max(arr[x]+i-1,l[u]),min(r[u],arr[x]+i-1+K)),tmp4=S(max(arr[x]+i-1,l[u]),min(r[u],arr[x]+i-1+K-1));
		for(int v:gr[u]){
			ade(s1[u][i],p1[v][i]),
			ade(s2[u][i],p2[v][i]),
			ade(r1[u][i],q1[v][i]),
			ade(r2[u][i],q2[v][i]);
		}
		q1[u][i]=add(r1[u][i]*tmp1%mod,s1[u][i]*tmp3%mod);
		q2[u][i]=add(r2[u][i]*tmp2%mod,s2[u][i]*tmp4%mod);
		p1[u][i]=s1[u][i]*tmp1%mod;
		p2[u][i]=s2[u][i]*tmp2%mod;
		if(i==t+1)continue;
		yg[u][i]=add(q1[u][i]-q2[u][i+1],mod);
		yf[u][i]=add(p1[u][i]-p2[u][i+1],mod);
	//	printf("[%d][%d] p1=%d, p2=%d, yf=%d\n",u,i,p1[u][i],p2[u][i],yf[u][i]);
	}
}//checked
inline void Lagrange(int t,int m){
	for(int u=1;u<=n;u++)
		for(int i=2;i<=t;i++)
			ade(yF[u][i],yF[u][i-1]),ade(yG[u][i],yG[u][i-1]);
	if(t>=m){
		for(int u=1;u<=n;u++)ade(sum,yF[u][m]),ade(suM,yG[u][m]);
		return;
	}
	pre[0]=suf[t+1]=1;
	for(int i=1;i<=t;i++)pre[i]=1ll*pre[i-1]*(m-i)%mod;
	for(int i=t;i;i--)suf[i]=1ll*suf[i+1]*(m-i)%mod;
	for(int i=1;i<=t;i++){
		int tmp=(1ll*pre[i-1]*suf[i+1]%mod*ijc[i-1]%mod*ijc[t-i]*((t-i&1)?-1:1)%mod+mod)%mod;
		for(int u=1;u<=n;u++){
			ade(sum,1ll*tmp*yF[u][i]%mod);
			ade(suM,1ll*tmp*yG[u][i]%mod);
		}
	}
}
void dfs0(int u,int p){
	for(int v:gg[u])if(v^p)dfs0(v,u),gr[u].push_back(v);
}
void dfs2(int u){
	if(u==1){
		for(int i=t+1;i;i--){
			const long long tmp1=max(0ll,min(r[u],arr[x]+i-1+K)-max(arr[x]+i-1,l[u])+1),tmp2=max(0ll,min(r[u],arr[x]+i-1+K-1)-max(arr[x]+i-1,l[u])+1),tmp3=S(max(arr[x]+i-1,l[u]),min(r[u],arr[x]+i-1+K)),tmp4=S(max(arr[x]+i-1,l[u]),min(r[u],arr[x]+i-1+K-1));
			for(int v:gr[u]){
				S1[v][i]=(s1[v][i]+(s1[u][i]-p1[v][i]+mod)%mod*tmp1%mod)%mod,
				S2[v][i]=(s2[v][i]+(s2[u][i]-p2[v][i]+mod)%mod*tmp2%mod)%mod,
				R1[v][i]=(r1[v][i]+((r1[u][i]-q1[v][i]+mod)%mod*tmp1%mod+(s1[u][i]-p1[v][i]+mod)%mod*tmp3%mod)%mod)%mod,
				R2[v][i]=(r2[v][i]+(r2[u][i]-q2[v][i]+mod)%mod*tmp2%mod+(s2[u][i]-p2[v][i]+mod)%mod*tmp4%mod)%mod;
			}
			Q1[u][i]=add(r1[u][i]*tmp1%mod,s1[u][i]*tmp3%mod);
			Q2[u][i]=add(r2[u][i]*tmp2%mod,s2[u][i]*tmp4%mod);
			P1[u][i]=s1[u][i]*tmp1%mod;
			P2[u][i]=s2[u][i]*tmp2%mod;
		}for(int i=t;i;i--){
			yG[u][i]=add(Q1[u][i]-Q2[u][i+1],mod);
			yF[u][i]=add(P1[u][i]-P2[u][i+1],mod);
		//	printf("[%d][%d] P1=%d, P2=%d, yF=%d\n",u,i,P1[u][i],P2[u][i],yF[u][i]);
		}
		for(int v:gr[u])dfs2(v);
		return;
	}
	for(int i=t+1;i;i--){
		const long long tmp1=max(0ll,min(r[u],arr[x]+i-1+K)-max(arr[x]+i-1,l[u])+1),tmp2=max(0ll,min(r[u],arr[x]+i-1+K-1)-max(arr[x]+i-1,l[u])+1),tmp3=S(max(arr[x]+i-1,l[u]),min(r[u],arr[x]+i-1+K)),tmp4=S(max(arr[x]+i-1,l[u]),min(r[u],arr[x]+i-1+K-1));
		for(int v:gr[u]){
			S1[v][i]=add(s1[v][i],(S1[u][i]-p1[v][i]+mod)*tmp1%mod),
			S2[v][i]=add(s2[v][i],(S2[u][i]-p2[v][i]+mod)*tmp2%mod),
			R1[v][i]=(r1[v][i]+((R1[u][i]-q1[v][i]+mod)*tmp1%mod+(S1[u][i]-p1[v][i]+mod)*tmp3%mod)%mod)%mod,
			R2[v][i]=(r2[v][i]+(R2[u][i]-q2[v][i]+mod)*tmp2%mod+(S2[u][i]-p2[v][i]+mod)*tmp4%mod)%mod;
		}
		Q1[u][i]=add(R1[u][i]*tmp1%mod,S1[u][i]*tmp3%mod);
		Q2[u][i]=add(R2[u][i]*tmp2%mod,S2[u][i]*tmp4%mod);
		P1[u][i]=S1[u][i]*tmp1%mod;
		P2[u][i]=S2[u][i]*tmp2%mod;
	}for(int i=t;i;i--){
		yG[u][i]=add(Q1[u][i]-Q2[u][i+1],mod);
		yF[u][i]=add(P1[u][i]-P2[u][i+1],mod);
	//	printf("[%d][%d] P1=%d, P2=%d, yF=%d\n",u,i,P1[u][i],P2[u][i],yF[u][i]);
	}
	for(int v:gr[u])dfs2(v);
}
signed main(){
	double tim1=clock();
	n=read(),K=read();
	const int U=n+3;
	jc[0]=ijc[0]=1;
	for(int i=1;i<=U;i++)jc[i]=1ll*jc[i-1]*i%mod;
	ijc[U]=qp(jc[U],mod-2);
	for(int i=U-1;i;i--)ijc[i]=(i+1ll)*ijc[i+1]%mod;//print(n+1,ijc);puts("|||");
	for(int i=1;i<=n;i++){
		l[i]=read(),r[i]=read();
		(_+=r[i]-l[i]+1)%=mod,(__+=S(l[i],r[i]))%=mod;
		arr[++tot]=r[i]-K,arr[++tot]=l[i],arr[++tot]=r[i]+1,arr[++tot]=l[i]-K-1,arr[++tot]=l[i]-K,arr[++tot]=r[i]-K+1;
	}
	sum=_,suM=__;
	sort(arr+1,arr+tot+1),tot=unique(arr+1,arr+tot+1)-arr-1;
	for(int i=1,u,v;i<n;i++)u=read(),v=read(),gg[u].push_back(v),gg[v].push_back(u);
	dfs0(1,0);
	double tim2=clock();
	for(x=1,t=min(U,arr[2]-arr[1]);x<tot;x++,t=min(U,arr[x+1]-arr[x])/*,puts("---")*/)
		dfs1(1),dfs2(1),Lagrange(t,arr[x+1]-arr[x]);
	printf("%lld\n%lld\n",1ll*sum*_12%mod,1ll*suM*_12%mod);
	double tim3=clock();
//	printf("%.0lf %.0lf",tim2-tim1,tim3-tim2);
}
posted @ 2022-05-14 18:03  pengyule  阅读(180)  评论(0编辑  收藏  举报