知识点 - 多项式插值法

知识点 - 多项式插值法

解决问题类型:

  • 已知f(0),f(1),f(2)…f(n),求一个次数界为 n n n 的多项式,满足这些取值。

  • n k n^k nk 的前缀和,或 n k n^k nk 的k阶前缀和公式(直接插值,不过会T。对于第一个问题,后面给出了组合数解法。)

公式blog

讲义

2.多项式插值算法

2.1 多项式插值的存在唯一性

多项式一直以来备受数学家们青睐,一方面它构造起来简单,另一方面它有非常美妙的性质,下面介绍多项式插值算法。

如果给定 n n n 个横纵坐标分别互不相同的点 ( x i , y i ) , i = 1 , 2 , … , n (x_i,y_i),i=1,2,\dots,n (xi,yi),i=1,2,,n,那么我们能否构造一个次数界为 n n n 的多项式函数,使得它的函数图像恰好经过这 n n n 个点?答案是肯定的,而且这个多项式函数是唯一的,证明如下:

设存在这样的一个多项式
f ( x ) = ∑ i = 0 n − 1 a i x i f(x)=\sum_{i=0}^{n-1}{a_ix^i} f(x)=i=0n1aixi
根据构造条件,有
{ f ( x 1 ) = y 1 f ( x 2 ) = y 2 … f ( x n ) = y n \begin{cases} f(x_1)=y_1\\ f(x_2)=y_2\\ \dots\\ f(x_n)=y_n\\ \end{cases} f(x1)=y1f(x2)=y2f(xn)=yn
将上述线性方程组中的 a i , i = 1 , 2 , … , n a_i,i=1,2,\dots,n ai,i=1,2,,n ,视为未知量,其系数矩阵的行列式 A A A 恰好为范德蒙行列式,
V ( x 1 , x 2 , ⋯   , x n ) = [ 1 1 ⋯ 1 x 1 x 2 ⋯ x n x 1 2 x 2 2 ⋯ x n 2 ⋮ ⋮ ⋮ x 1 n − 1 x 2 n − 1 ⋯ x n n − 1 ] V(x_1,x_2,\cdots ,x_{n})=\begin{bmatrix} {1}&{1}&{\cdots}&{1}\\ {x_{1}}&{x_{2}}&{\cdots}&{x_{n}}\\ {x_{1}^2}&{x_2^2}&{\cdots}&{x_{n}^2}\\ {\vdots}&{\vdots}&{}&{\vdots}\\ {x_{1}^{n-1}}&{x_{2}^{n-1}}&{\cdots}&{x_{n}^{n-1}}\\ \end{bmatrix} V(x1,x2,,xn)= 1x1x12x1n11x2x22x2n11xnxn2xnn1
故(数学归纳法证明)
d e t ( A ) = ∏ 1 ≤ i < j ≤ n ( x j − x i ) det(A)=\prod_{1\le i<j\le n}{(x_j-x_i)} det(A)=1i<jn(xjxi)
x i 互不相同 , i = 1 , 2 , … , n x_i互不相同,i=1,2,\dots,n xi互不相同,i=1,2,,n,因此 d e t ( A ) ≠ 0 det(A)\neq 0 det(A)=0,方程组有唯一解

2.2 Lagrange多项式插值 O(N^2)

那么问题就来了,如何求这个多项式呢?利用高斯消元解上述线性方程组是一个办法,算法的复杂度为 O ( n 3 ) O(n^3) O(n3) ,其实这个多项式可以直接被构造出来
f ( x ) = ∑ i = 1 n ( ∏ j ≠ i x − x j x i − x j ) y i f(x)=\sum_{i=1}^n\left(\prod_{j\neq i}{\frac{x-x_j}{x_i-x_j}}\right)y_i f(x)=i=1n j=ixixjxxj yi
这就是Lagrange插值公式,不难验证其次数至多为 n − 1 n-1 n1 ,且满足上述线性方程组,因此这就是我们要求的多项式。

参考代码:O(n^2)

//输出在x处的取值
#include <bits/stdc++.h>
#define rep(i,a,b) for(ll i=a;i<=b;i++)
using namespace std;
typedef long long ll;
typedef long double type;
type lagrange(vector<type> x,vector<type> y,type X)
{
	int n=x.size()-1;
	type ans=0;
	rep(k,0,n)
	{
		type temp=y[k];
		rep(j,0,n)if(j!=k)temp*=(X-x[j])/(x[k]-x[j]);
		ans+=temp;
	}
	return ans;
}
int main()
{
	vector<type> x={0,1,2,3};
	vector<type> y={0,1,4,9};
	type X;
	while(cin>>X)cout<<lagrange(x,y,X)<<endl;
	return 0;
}

在ACM竞赛中,如果某个组合数学类题目刚好是输入一个整数 n n n ,输出一个多项式函数的值 f ( n ) f(n) f(n) ,那么上述算法只需要放入某几项即可,不需要推导复杂的公式,例如2018年icpc南京现场赛的G题,将上述算法的除法修改为乘法逆元即可,至于最终的公式是啥以及如何推导,本文暂不讨论。

参考代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mo=1e9+7;
ll fpow(ll a,ll b){
	ll ans=1;
	while(b>0){if(b&1)ans=ans*a%mo;b>>=1;a=a*a%mo;}
	return ans;
}
ll lagrange(vector<ll> x,vector<ll> y,ll X){
	auto p=y.begin();
	ll ans=0;
	for(auto k:x){
		ll a=*p++%mo,b=1;
		for(auto j:x)if(j!=k)a=(X-j)%mo*a%mo,b=(k-j)%mo*b%mo;
		ans=(ans+mo+a*fpow(b,mo-2)%mo)%mo;
	}
	return ans;
}
int main(){
	vector<ll> x={0,1,2,3,4};
	vector<ll> y={0,1,5,15,35};
	ll n;
	while(cin>>n)cout<<lagrange(x,y,n)<<endl;
	return 0;
}

其实在1.1的参考代码中已经给出了输出插值多项式的函数,可用如下方式调用

int main(){
	vector<type> x={frac(0,1),frac(1,1),frac(2,1),frac(3,1),frac(4,1)};
	vector<type> y={frac(0,1),frac(1,1),frac(5,1),frac(15,1),frac(35,1)};
	Poly f=Lagrange(x,y);
	cout<<f;
	ll X;
	while(cin>>X)cout<<f(frac(X,1))<<endl;
	return 0;
}

运行结果

(1/24)x^4+(1/4)x^3+(11/24)x^2+(1/4)x^1+(0)
2.2.1 x连续时的O(N)优化

x i ∈ [ 0 , n ] x_i\in[0,n] xi[0,n] 时,注意到下式的分母会变成阶乘
f ( x ) = ∑ i = 1 n ( ∏ j ≠ i x − x j x i − x j ) y i f(x)=\sum_{i=1}^n\left(\prod_{j\neq i}{\frac{x-x_j}{x_i-x_j}}\right)y_i f(x)=i=1n j=ixixjxxj yi
前面是递减的连乘,直到 i = = j i==j i==j后面是带负号的,于是我们将分母写出来有
i ! ( − 1 ) ( n − i ) ( n − i ) ! i!(-1)^{(n-i)}(n-i)! i!(1)(ni)(ni)!

对于分子可以预处理出连乘 ∏ j = 1 n ( x − j ) \prod_{j=1}^{n} (x-j) j=1n(xj),然后第 i 个的分子为 S ( i , x ) = ∏ j = 1 n ( x − j ) x − i S(i,x) = \frac{\prod_{j=1}^{n} (x-j)}{x-i} S(i,x)=xij=1n(xj) (实现用逆元或维护前后缀积)。

这样便 O ( 1 ) O(1) O(1)地求出 ( ∏ j ≠ i x − x j x i − x j ) y i \left(\prod_{j\neq i}{\frac{x-x_j}{x_i-x_j}}\right)y_i (j=ixixjxxj)yi。总复杂度 O ( N ) O(N) O(N)

南昌邀请赛的银牌题Polynomial: 题库链接

2.2.2 O(n)在线单点增加操作: 重心拉格朗日插值法

可以将拉格朗日基本多项式重新写为:(公式本地可见)

[外链图片转存失败(img-tO1Ptqxl-1565618397136)(http://upload.wikimedia.org/math/f/6/5/f65c55d7b4c330438e7a68482d1590e7.png)]

定义重心权[7][8]

[外链图片转存失败(img-8NiabXBb-1565618397137)(http://upload.wikimedia.org/math/f/5/b/f5b96ca21fad59d37cd3ea6a4c70246a.png)]

上面的表达式可以简化为:

[外链图片转存失败(img-rXSJuUtt-1565618397138)(http://upload.wikimedia.org/math/c/f/0/cf0ee67a5ae90253b230f700a86cb5c1.png)]

于是拉格朗日插值多项式变为:

[外链图片转存失败(img-V2KRbr1n-1565618397139)(http://upload.wikimedia.org/math/e/2/b/e2b06ed5610f4fcc046fef1b0a9f493e.png)]

即所谓的重心拉格朗日插值公式(第一型)或改进拉格朗日插值公式。它的优点是当插值点的个数增加一个时,将每个w_{j}都除以[外链图片转存失败(img-iPPfzXMU-1565618397140)(http://upload.wikimedia.org/math/7/e/9/7e9644833883af0bcd5e70adb818536b.png)],就可以得到新的重心权[外链图片转存失败(img-0ewyQlBX-1565618397141)(http://upload.wikimedia.org/math/e/9/2/e926afcf008b0e517216b346c6883710.png)],计算复杂度为[外链图片转存失败(img-AW5mUFDb-1565618397142)(http://upload.wikimedia.org/math/d/4/3/d43308deb858cf186f862f2451af08b6.png)],比重新计算每个基本多项式所需要的复杂度[外链图片转存失败(img-0FZGN8jw-1565618397142)(http://upload.wikimedia.org/math/5/6/d/56d1e7b3b345d81e79b9ff2a0023adb1.png)]降了一个量级。

2.3 Newton多项式插值

Lagrange插值算法对于ACM竞赛中的相关题目来说可能已经足够了,但Lagrange插值算法最初并不是为了这么用的,它的主要用途是构造一个多项式来逼近另外一个函数,例如我们用计算机可能没办法计算三角函数 s i n ( x ) sin(x) sin(x) 的精确值,但是如果已知其中某些点的值,就可以构造这样的一个多项式来逼近 s i n ( x ) sin(x) sin(x) ,就可以计算其近似值,误差即为 s i n ( x ) sin(x) sin(x) 的泰勒展开式中的Lagrange余项。对于复杂的函数,如果增加一个插值点,那么多项式就需要重新构造,求解单点处的值的复杂度为 O ( n 2 ) O(n^2) O(n2) ,于是数学家们想出了另一个算法—Newton多项式插值

首先定义差商:

零阶差商
F ( x i ) = y i F(x_i)=y_i F(xi)=yi
n n n 阶差商
F ( x 1 , x 2 , … , x n + 1 ) = F ( x 2 , x 3 , … , x n + 1 ) − F ( x 1 , x 2 , … , x n ) x n − x 1 F(x_1,x_2,\dots,x_{n+1})=\frac{F(x_2,x_3,\dots,x_{n+1})-F(x_1,x_2,\dots,x_{n})}{x_n-x_1} F(x1,x2,,xn+1)=xnx1F(x2,x3,,xn+1)F(x1,x2,,xn)
那么
f ( x ) = ∑ i = 1 n ( F ( x 1 , … , x i ) ∏ j = 1 i ( x − x j ) ) f(x)=\sum_{i=1}^n{\left(F(x_1,\dots,x_i)\prod_{j=1}^i(x-x_j)\right)} f(x)=i=1n(F(x1,,xi)j=1i(xxj))
这样一来,这个算法就有了很好的继承性,每次添加一个插值点,复杂度为 O ( n ) O(n) O(n) ,每次计算单点处的值,复杂度为 O ( n ) O(n) O(n) (请参考秦九韶算法)。

参考代码(连续函数)

#include <bits/stdc++.h>
#define rep(i,a,b) for(ll i=a;i<=b;i++)
using namespace std;
typedef long long ll;
typedef long double type;
class NewtonPoly{
	public:
	type f[105],d[105],x[105];
	ll n=0;
	void add(type X,type Y){
		x[n]=X,f[n]=Y;
		rep(i,1,n)f[n-i]=(f[n-i+1]-f[n-i])/(x[n]-x[n-i]);
		d[n++]=f[0];
	}
	type cal(type X){
		type ans=0,t=1;
		rep(i,0,n-1)ans+=d[i]*t,t*=X-x[i];
		return ans;
	}
}P;
int main(){
	P.add(0,0);
	P.add(1,1);
	P.add(2,5);
	P.add(3,15);
	P.add(4,35);
	type x;
	while(cin>>x)cout<<P.cal(x)<<endl;
	return 0;
}

参考代码(离散函数,取余数,可用于ACM竞赛)

#include <bits/stdc++.h>
#define rep(i,a,b) for(ll i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const ll mo=1e9+7;
ll fpow(ll a,ll b){
	ll ans=1;
	while(b>0){if(b&1)ans=ans*a%mo;b>>=1;a=a*a%mo;}
	return ans;
}
class NewtonPoly{
	public:
	ll f[105],d[105],x[105],n=0;
	void add(ll X,ll Y){
		x[n]=X,f[n]=Y%mo;
		rep(i,1,n)f[n-i]=(f[n-i+1]-f[n-i])%mo*fpow((x[n]-x[n-i])%mo,mo-2)%mo;
		d[n++]=f[0];
	}
	ll cal(ll X){
		ll ans=0,t=1;
		rep(i,0,n-1)ans=(ans+d[i]*t)%mo,t=(X-x[i])%mo*t%mo;
		return ans+mo*(ans<0);
	}
}P;
int main(){
	P.add(0,0);
	P.add(1,1);
	P.add(2,5);
	P.add(3,15);
	P.add(4,35);
	ll x;
	while(cin>>x)cout<<P.cal(x)<<endl;
	return 0;
}

2.5 高维插值整式

我们解决了一维的情况,二维的情况可由一维的Lagrange插值函数推广得来,更高维也类似
f ( x , y ) = ∑ n = 1 N ∑ m = 1 M ( ∏ i ≠ n x − x i x n − x i ) ( ∏ j ≠ m y − y j y m − y j ) z n , m f(x,y)=\sum_{n=1}^N\sum_{m=1}^M\left(\prod_{i\neq n}{\frac{x-x_i}{x_n-x_i}}\right)\left(\prod_{j\neq m}{\frac{y-y_j}{y_m-y_j}}\right)z_{n,m} f(x,y)=n=1Nm=1M i=nxnxixxi j=mymyjyyj zn,m
参考代码(连续函数)

#include <bits/stdc++.h>
#define rep(i,a,b) for(ll i=a;i<=b;i++)
using namespace std;
typedef long long ll;
typedef long double type;
type lagrange2(vector<type> x,vector<type> y,vector<vector<type> > z,type X,type Y){
	int M=x.size()-1,N=y.size()-1;
	type ans=0;
	rep(m,0,M)rep(n,0,N){
		type t=z[m][n];
		rep(i,0,M)if(i!=m)t*=(X-x[i])/(x[m]-x[i]);
		rep(i,0,N)if(i!=n)t*=(Y-y[i])/(y[n]-y[i]);
		ans+=t;
	}
	return ans;
}
int main(){
	vector<type> x={1,2};
	vector<type> y={3,4};
	vector<vector<type> > z={{3,4},{6,8}};
	type X,Y;
	while(cin>>X>>Y)cout<<lagrange2(x,y,z,X,Y)<<endl;
	return 0;
}

参考代码(离散函数,取余数,可用于ACM竞赛)

#include <bits/stdc++.h>
#define rep(i,a,b) for(ll i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const ll mo=1e9+7;
ll fpow(ll a,ll b){
	ll ans=1;
	while(b>0){if(b&1)ans=ans*a%mo;b>>=1;a=a*a%mo;}
	return ans;
}
ll lagrange2(vector<ll> x,vector<ll> y,vector<vector<ll> > z,ll X,ll Y){
	ll M=x.size()-1,N=y.size()-1,ans=0;
	rep(m,0,M)rep(n,0,N){
		ll a=z[m][n]%mo,b=1;
		rep(i,0,M)if(i!=m)a=(X-x[i])%mo*a%mo,b=(x[m]-x[i])%mo*b%mo;
		rep(i,0,N)if(i!=n)a=(Y-y[i])%mo*a%mo,b=(y[n]-y[i])%mo*b%mo;
		ans=(ans+a*fpow(b,mo-2)%mo)%mo;
	}
	return ans+mo*(ans<0);
}
int main(){
	vector<ll> x={0,1,2,3,4,5,6,7,8,9};
	vector<ll> y={0,1,2,3,4,5,6,7,8,9};
	vector<vector<ll> > z={
		{0,0,0,0,0,0,0,0,0,0},
		{0,1,2,3,4,5,6,7,8,9},
		{-2,2,6,10,14,18,22,26,30,34},
		{-10,0,10,20,30,40,50,60,70,80},
		{-30,-10,10,30,50,70,90,110,130,150},
		{-70,-35,0,35,70,105,140,175,210,245},
		{-140,-84,-28,28,84,140,196,252,308,364},
		{-252,-168,-84,0,84,168,252,336,420,504},
		{-420,-300,-180,-60,60,180,300,420,540,660},
		{-660,-495,-330,-165,0,165,330,495,660,825}
	};
	ll X,Y;
	while(cin>>X>>Y){
		cout<<lagrange2(x,y,z,X,Y)<<endl;
	}
	return 0;
}

1. 多项式的前缀和

1.1 n k n^k nk 的前缀和

首先来看几个众所周知的公式
∑ i = 1 n 1 = n \sum_{i=1}^n{1}=n i=1n1=n

∑ i = 1 n i = n ( n + 1 ) 2 \sum_{i=1}^n{i}=\frac{n(n+1)}{2} i=1ni=2n(n+1)

∑ i = 1 n i 2 = n ( n + 1 ) ( 2 n + 1 ) 6 \sum_{i=1}^n{i^2}=\frac{n(n+1)(2n+1)}{6} i=1ni2=6n(n+1)(2n+1)

用数学归纳法很容易验证上述公式的正确性,但是对于任何给定的非负整数 k k k ,如何求出 ∑ i = 1 n i k \sum_{i=1}^n{i^k} i=1nik 呢?下面给出一种利用杨辉三角的计算方法。
1 1 1 1 1 1 2 3 4 5 1 3 6 10 15 1 4 10 20 35 1 5 15 35 70 \begin{matrix} 1&1&1&1&1\\ 1&2&3&4&5\\ 1&3&6&10&15\\ 1&4&10&20&35\\ 1&5&15&35&70\\ \end{matrix} 111111234513610151410203515153570

杨辉三角的自然数形式

C 0 0 C 1 1 C 2 2 C 3 3 C 4 4 C 1 0 C 2 1 C 3 2 C 4 3 C 5 4 C 2 0 C 3 1 C 4 2 C 5 3 C 6 4 C 3 0 C 4 1 C 5 2 C 6 3 C 7 4 C 4 0 C 5 1 C 6 2 C 7 3 C 8 4 \begin{matrix} C_0^0&C_1^1&C_2^2&C_3^3&C_4^4\\ C_1^0&C_2^1&C_3^2&C_4^3&C_5^4\\ C_2^0&C_3^1&C_4^2&C_5^3&C_6^4\\ C_3^0&C_4^1&C_5^2&C_6^3&C_7^4\\ C_4^0&C_5^1&C_6^2&C_7^3&C_8^4\\ \end{matrix} C00C10C20C30C40C11C21C31C41C51C22C32C42C52C62C33C43C53C63C73C44C54C64C74C84

杨辉三角的组合数形式

不难发现,第 i i i列的前 n n n个数字之和刚好等于第 i + 1 i+1 i+1列第 n n n个数字,即
C k k + C k + 1 k + ⋯ + C k + n k = C k + n + 1 k + 1 C_k^k+C_{k+1}^k+\dots+C_{k+n}^k=C_{k+n+1}^{k+1} Ckk+Ck+1k++Ck+nk=Ck+n+1k+1
根据杨辉恒等式 C n k = C n − 1 k − 1 + C n − 1 k C_n^k=C_{n-1}^{k-1}+C_{n-1}^k Cnk=Cn1k1+Cn1k 左边 = C k k + C k + 1 k + ⋯ + C k + n k = C k + 1 k + 1 + C k + 1 k + ⋯ + C k + n k = C k + 2 k + 1 + ⋯ + C k + n k = ⋯ = C k + n + 1 k + 1 = 右边 左边=C_k^k+C_{k+1}^k+\dots+C_{k+n}^k=C_{k+1}^{k+1}+C_{k+1}^k+\dots+C_{k+n}^k=C_{k+2}^{k+1}+\dots+C_{k+n}^k=\dots=C_{k+n+1}^{k+1}=右边 左边=Ckk+Ck+1k++Ck+nk=Ck+1k+1+Ck+1k++Ck+nk=Ck+2k+1++Ck+nk==Ck+n+1k+1=右边

换言之,除第一列外,每一列都是前一列的“前缀和”,而每一列的数字,都是 C n k ( k = 列数 − 1 ) C_n^k(k=列数-1) Cnk(k=列数1) 的形式,其本质就是 n n n 的多项式,例如:
第一列 : C n 0 = 1 第一列:C_n^0=1 第一列:Cn0=1

第二列: C n 1 = ∑ i = 0 n − 1 C i 0 = ∑ i = 0 n − 1 1 = n 第二列:C_n^1=\sum_{i=0}^{n-1}{C_i^0}=\sum_{i=0}^{n-1}{1}=n 第二列:Cn1=i=0n1Ci0=i=0n11=n

第三列: C n 2 = ∑ i = 1 n − 1 C i 1 = ∑ i = 1 n − 1 i = n ( n − 1 ) 2 第三列:C_n^2=\sum_{i=1}^{n-1}{C_i^1}=\sum_{i=1}^{n-1}{i}=\frac{n(n-1)}{2} 第三列:Cn2=i=1n1Ci1=i=1n1i=2n(n1)

第四列: C n 3 = ∑ i = 2 n − 1 C i 2 = ∑ i = 2 n − 1 i ( i − 1 ) 2 = n ( n − 1 ) ( n − 2 ) 6 第四列:C_n^3=\sum_{i=2}^{n-1}{C_i^2}=\sum_{i=2}^{n-1}{\frac{i(i-1)}{2}}=\frac{n(n-1)(n-2)}{6} 第四列:Cn3=i=2n1Ci2=i=2n12i(i1)=6n(n1)(n2)

根据第三列,得到
∑ i = 1 n i = ∑ i = 1 n − 1 i + n = n ( n + 1 ) 2 \sum_{i=1}^n{i}=\sum_{i=1}^{n-1}{i}+n=\frac{n(n+1)}{2} i=1ni=i=1n1i+n=2n(n+1)
根据第四列,得到
∑ i = 2 n − 1 i ( i − 1 ) 2 = 1 2 ∑ i = 2 n − 1 i 2 − 1 2 ∑ i = 2 n − 1 i = 1 2 ( ∑ i = 1 n i 2 − 1 − n 2 ) − ( n + 1 ) ( n − 2 ) 4 = n ( n − 1 ) ( n − 2 ) 6 \sum_{i=2}^{n-1}{\frac{i(i-1)}{2}}=\frac{1}{2}\sum_{i=2}^{n-1}{i^2}-\frac{1}{2}\sum_{i=2}^{n-1}{i}=\frac{1}{2}\left(\sum_{i=1}^{n}{i^2}-1-n^2\right)-\frac{(n+1)(n-2)}{4}=\frac{n(n-1)(n-2)}{6} i=2n12i(i1)=21i=2n1i221i=2n1i=21(i=1ni21n2)4(n+1)(n2)=6n(n1)(n2)
进而得到
∑ i = 1 n i 2 = n ( n + 1 ) ( 2 n + 1 ) 6 \sum_{i=1}^n{i^2}=\frac{n(n+1)(2n+1)}{6} i=1ni2=6n(n+1)(2n+1)
代码太长,令开一篇给出。

例题

南昌邀请赛的银牌题Polynomial:题库链接 题意:x取值连续的拉格朗日插值+前缀和的板题

代码

#include<bits/stdc++.h>
using namespace std;
const int mod=9999991;
const int N=1005;
long long a[N];
long long sum[N];
long long fac[N];
long long inv[N];
long long _inv[mod+5];
long long quickmod(long long a,long long b)
{
    long long ans=1;
    while(b)
    {
        if(b%2==1)
            ans=ans*a%mod;
        a=a*a%mod;
        b=b/2;
    }
    return ans;
}
int main()
{
    fac[0]=1;
    for(int i=1; i<N; i++)
        fac[i]=1ll*(fac[i-1])*i%mod;
    inv[N-1]=quickmod(fac[N-1],mod-2);
    for(int i=N-2; i>=0; i--)
        inv[i]=(1ll*inv[i+1]*(i+1))%mod;
    _inv[0]=_inv[1]=1;
    for(int i=2;i<mod+5;i++)
        _inv[i]=((mod-mod/i)*_inv[mod%i])%mod;
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        memset(a,0,sizeof(a));
        memset(sum,0,sizeof(sum));
        long long z=1;
        for(int i=0; i<=n; i++)
        {
            scanf("%d",&a[i]);
            a[i]=a[i]%mod;
            z=z*((n+1-i+mod)%mod)%mod;
            if(i!=0)
                sum[i]=(sum[i-1]+a[i])%mod;
            else
                sum[i]=a[i];
        }
        for(int i=0; i<=n; i++)
        {
            if((n-i)%2==1)
                a[n+1]=(a[n+1]-z%mod*_inv[n+1-i]%mod*inv[n-i]%mod*inv[i]%mod%mod*a[i]%mod+mod)%mod;
            else
                a[n+1]=(a[n+1]+z%mod*_inv[n+1-i]%mod*inv[n-i]%mod*inv[i]%mod%mod*a[i]%mod+mod)%mod;
        }
        sum[n+1]=(sum[n]+a[n+1])%mod;
        while(m--)
        {
            int l,r;
            scanf("%d%d",&l,&r);
            if(r<=n+1)
            {
                printf("%d\n",(sum[r]-sum[l-1]+mod)%mod);
                continue;
            }
            long long x=1,y=1;
            for(int i=0; i<=n+1; i++)
            {
                x=x*(r-i)%mod;
                y=y*(l-1-i)%mod;
            }
            long long ans=0;
            for(int i=0; i<=n+1; i++)
            {
                if((n+1-i)%2==1)
                    ans=(ans-x%mod*_inv[r-i]%mod*inv[n+1-i]%mod*inv[i]%mod%mod*sum[i]%mod+mod)%mod;
                else
                    ans=(ans+x%mod*_inv[r-i]%mod*inv[n+1-i]%mod*inv[i]%mod%mod*sum[i]%mod+mod)%mod;
            }
            if(l-1<=n+1)
                ans=(ans-sum[l-1]+mod)%mod;
            else
            {
                for(int i=0; i<=n+1; i++)
                {
                    if((n+1-i)%2==1)
                        ans=(ans+y%mod*_inv[l-1-i]%mod*inv[n+1-i]%mod*inv[i]%mod%mod*sum[i]%mod+mod)%mod;
                    else
                        ans=(ans-y%mod*_inv[l-1-i]%mod*inv[n+1-i]%mod*inv[i]%mod%mod*sum[i]%mod+mod)%mod;
                }
            }
            printf("%lld\n",ans);
        }
    }
    return 0;
}
/*
1
3 2
1 10 49 142
6 7
95000 100000
*/




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Best KeyBoard

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值