解题报告(三)多项式求值与插值(拉格朗日插值)(ACM / OI)

整理的算法模板合集: ACM模板

点我看算法全家桶系列!!!

实际上是一个全新的精炼模板整合计划


繁凡出品的全新系列:解题报告系列 —— 超高质量算法题单,配套我写的超高质量的题解和代码,题目难度不一定按照题号排序,我会在每道题后面加上题目难度指数( 1 ∼ 5 1 \sim 5 15),以模板题难度 1 1 1 为基准。


这样大家在学习算法的时候就可以执行这样的流程:

%
阅读【学习笔记】 / 【算法全家桶】学习算法 ⇒ \Rightarrow 阅读相应算法的【解题报告】获得高质量题单 ⇒ \Rightarrow 根据一句话题解的提示尝试自己解决问题 ⇒ \Rightarrow 点开详细题解链接学习巩固(好耶)
%
要是26个英文字母用完了我就接上24个希腊字母,我就不信50道题不够我刷的hhh

%
解题报告系列合集:【解题报告系列】超高质量题单 + 题解(ICPC / CCPC / NOIP / NOI / CF / AT / NC / P / BZOJ)

本题单前置知识:【学习笔记】多项式全家桶(包含全套证明)

拉格朗日插值法


有拉格朗日插值公式:

f ( x ) = ∑ i = 1 n y i ∏ j ≠ i x − x j x i − x j f(x)=\sum_{i=1}^ny_i\prod_{j\neq i}\dfrac {x-x_j} {x_i-x_j} f(x)=i=1nyij=ixixjxxj

A. P4781 【模板】拉格朗日插值

Weblink

https://www.luogu.com.cn/problem/P4781

Problem

解题报告(三)多项式求值与插值(拉格朗日插值)(ACM / OI)_第1张图片

我们将 n n n个点带入拉格朗日插值公式计算 f ( k ) f(k) f(k) 即可。时间复杂度 O ( n 2 ) O(n^2) O(n2)

注意本题还要求逆元,为了防止求逆元的时间复杂度影响整体的时间复杂度,所以我们分别计算出分子和分母,再将分子乘进分母的逆元,累加进最后的答案,时间复杂度的瓶颈就不会在求逆元上,总体的时间复杂度为 O ( n 2 ) O(n^2) O(n2)

Code

本题需要累乘,会爆int,记得开long long

#include 
#include 
#include 
#include 

using namespace std;

const int N = 500007;
typedef long long ll;
const int mod = 998244353;
ll n, m, k;

struct Point
{
     
    ll x, y;
}A[N];

ll qpow(ll a, ll b, ll c)
{
     
    ll res = 1;
    while(b) {
     
        if(b & 1) res = res * a % c;
        a = a * a % c;
        b >>= 1;
    }
    return res;
}

ll inv(ll x) {
     return qpow(x, mod - 2, mod);}

int main()
{
     
    scanf("%lld%lld", &n, &k);
    for(int i = 1; i <= n; ++ i) {
     
        scanf("%lld%lld", &A[i].x, &A[i].y);
    }
    ll ans = 0;

    for(int i = 1; i <= n; ++ i) {
     
        ll s1 = A[i].y % mod;
        ll s2 = 1ll;
        for(int j = 1; j <= n; ++ j) {
     
            if(i != j) {
     
                s1 = s1 * (k - A[j].x) % mod;
                s2 = s2 * (A[i].x - A[j].x) % mod;
            }
        }
        ans += s1 * inv(s2) % mod;
    }
    printf("%lld\n", (ans % mod + mod) % mod);
    return 0;
}

我们使用拉格朗日插值公式,对于一个数 k k k ,我们很容易在 O ( n 2 ) O(n^2) O(n2) 时间求得 F ( k ) F(k) F(k) 的数值,如果 x i x_i xi 是连续的,我们甚至可以利用预处理在 O ( n ) O(n) O(n) 时间内得到 F ( k ) F(k) F(k) 的数值。
但是如果 x i x_i xi 不连续,又有多组查询,就需要得到这个多项式的系数以保证求一个函数值的时间为 O ( n ) O(n) O(n)
解题报告(三)多项式求值与插值(拉格朗日插值)(ACM / OI)_第2张图片

重心拉格朗日插值法

考虑对拉格朗日插值公式进行优化:

f ( x ) = ∑ i = 1 n y i ∏ j ≠ i x − x j x i − x j f(x)=\sum_{i=1}^n y_i\prod_{j\neq i}\dfrac {x-x_j} {x_i-x_j} f(x)=i=1nyij=ixixjxxj

h = ∏ i = 1 n x − x i h=\prod_{i=1}^n x-x_i h=i=1nxxi
带入得:
= h ∑ i = 1 n ∏ j ≠ i y i ( x i − x j ) ( x − x i ) =h\sum_{i=1}^n \prod_{j\neq i}\dfrac {y_i} {(x_i-x_j)(x-x_i)} =hi=1nj=i(xixj)(xxi)yi


t i = ∏ j ≠ i y i x i − x j t_i=\prod_{j\neq i} \dfrac {y_i} {x_i-x_j} ti=j=ixixjyi

带入得:

= h ∑ i = 1 n t i x − x i =h\sum_{i=1}^n \dfrac {t_i} {x-x_i} =hi=1nxxiti

在每次加入一个新点时,计算出它的 t i t_i ti ,并且更新别的点的 t i t_i ti ,时间复杂度 O ( n ) O(n) O(n),加入 n n n 个点就是 n 2 n^2 n2

这样我们就可以 O ( n ) O(n) O(n) h h h ,然后再 O ( n ) O(n) O(n) 求出 ∑ \sum ,总时间复杂度为 O ( n ) O(n) O(n)

B. 拉格朗日插值法求系数

Solution

首先观察式子,发现对于每个 i i i , 上面部分是 ( x − x 1 ) × ( x − x 2 ) … × ( x − x n ) ( x − x i ) \cfrac{(x-x_1) \times (x-x_2)… \times (x-x_n)}{(x-x_i)} (xxi)(xx1)×(xx2)×(xxn), 下面那部分和 y i y_i yi 都是常数。

所以可以 O ( n 2 ) O(n^2) O(n2) 处理出 ( x − x 1 ) × ( x − x 2 ) … × ( x − x n ) (x-x_1) \times (x-x_2)… \times (x-x_n) (xx1)×(xx2)×(xxn) 这个 n n n 次多项式,然后通过模拟长除法 O ( n ) O(n) O(n)时间内可以得到 ( x − x 1 ) × ( x − x 2 ) … × ( x − x n ) ( x − x i ) \cfrac{(x-x_1) \times (x-x_2)… \times (x-x_n)}{(x-x_i)} (xxi)(xx1)×(xx2)×(xxn),然后常系数直接 O ( n ) O(n) O(n) 暴力算出来,就得到了 x i x_i xi 对应的多项式,最后把所有多项式加起来就得到了最终的系数。

Code

const int maxn = 5007;
ll mod = 998244353;

ll qpow(ll a, ll b) {
     
    ll res = 1;
    while (b) {
     
        if (b & 1)
            res = res * a % mod;
        a = a * a % mod, b >>= 1;
    }
    return res;
}
ll a[maxn], b[maxn], c[maxn], temp[maxn];
ll x[maxn], y[maxn];
int n;
void mul(ll *f, int len, ll t) {
      //len为多项式的次数+1,函数让多项式f变成f*(x+t)
    for (int i = len; i > 0; --i)
        temp[i] = f[i], f[i] = f[i - 1];
    temp[0] = f[0], f[0] = 0;
    for (int i = 0; i <= len; ++i)
        f[i] = (f[i] + t * temp[i]) % mod;
}
void dev(ll *f, ll *r, ll t) {
      //f是被除多项式的系数,r保存f除以x+t的结果
    for (int i = 0; i <= n; ++i)
        temp[i] = f[i];
    for (int i = n; i > 0; --i) {
     
        r[i - 1] = temp[i];
        temp[i - 1] = (temp[i - 1] - t * temp[i]) % mod;
    }
    return;
}
void lglr() {
     
    memset(a, 0, sizeof a);
    b[1] = 1, b[0] = -x[1];
    for (int i = 2; i <= n; ++i) {
     
        mul(b, i, -x[i]);
    }//预处理(x-x1)*(x-x2)...*(x-xn)
    for (int i = 1; i <= n; ++i) {
     
        ll fz = 1;
        for (int j = 1; j <= n; ++j) {
     
            if (j == i)
                continue;
            fz = fz * (x[i] - x[j]) % mod;
        }
        fz = qpow(fz, mod - 2);
        fz = fz * y[i] % mod; //得到多项式系数
        dev(b, c, -x[i]);//得到多项式,保存在b数组
        for (int j = 0; j < n; ++j)
            a[j] = (a[j] + fz * c[j]) % mod;
    }
}
int main() {
     
    ll k;
    cin >> n >> k;
    for (int i = 1; i <= n; ++i)
        scanf("%lld%lld", &x[i], &y[i]);
    lglr();
    ll ans = 0;
    ll res = 1;
    for (int i = 0; i < n; ++i) {
     
        ans = (ans + res * a[i]) % mod;
        res = res * k % mod;
    }
    ans = (ans + mod) % mod;
    cout << ans << endl;
}

C. 2019ICPC 南昌邀请赛 B. Polynomial

Problem

解题报告(三)多项式求值与插值(拉格朗日插值)(ACM / OI)_第3张图片

n ≤ 1000 , m ≤ 2000 , 1 ≤ L ≤ R ≤ 9999990 n\le 1000, m\le 2000, 1\le L\le R\le 9999990 n1000,m2000,1LR9999990

Solution

给定了 f i f_i fi i ∈ 0 ∼ n i\in 0\sim n i0n n n n 次多项式给定 n + 1 n+1 n+1 个值,并且还都是连续的,显然可以使用拉格朗日插值 O ( n ) O(n) O(n) 计算多项式 f ( x ) f(x) f(x)。题目中有 m m m 次询问,每次询问的是一个区间的取值,显然可以用前缀和来维护。

S ( x ) S(x) S(x) f i f_i fi 的前缀和,则答案为 S ( R ) − S ( L − 1 ) S(R)-S(L-1) S(R)S(L1)

而我们需要预处理出 S ( 1 ) ∼ S ( 9999990 ) S(1)\sim S(9999990) S(1)S(9999990),直接插值的话时间复杂度 O ( n × 9999990 ) O(n\times9999990) O(n×9999990)

我们知道 n n n 次多项式的前缀和是 n + 1 n+1 n+1 次的多项式,也就意味着 S ( x ) S(x) S(x) 需要用 n + 2 n+2 n+2 个点来通过拉格朗日插值求解。但是我们只有 n + 1 n+1 n+1 个点,我们可以利用拉格朗日插值法求出 f ( n + 1 ) f(n+1) f(n+1),这样就有了 n + 2 n+2 n+2 个点。我们只需要对 S ( x ) S(x) S(x) 进行插值即可。

Time

O ( T × m × n ) O(T\times m\times n) O(T×m×n)

Code

#include 

using namespace std;
const int N = 5007, mod = 9999991;

#define int long long

int n, m, t;
int inv[mod + 7], infact[mod + 7];
int sum[N], a[N];
 

void init(int n)
{
     
	inv[1] = 1;
	for(int i = 2; i <= n; ++ i)
		inv[i] = (mod - mod / i) * inv[mod % i] % mod;
	infact[0] = 1;
	for(int i = 1; i <= n; ++ i) 
		infact[i] = infact[i - 1] * inv[i] % mod;
}

int lagrange(int x, int *a, int n)
{
     
	int res = 0;
	int p = 1;
	for(int i = 0; i <= n; ++ i)
		p = p * (x - i) % mod;
	for(int i = 0; i <= n; ++ i) {
     
		int f = (n - i) & 1 ? -1 : 1;
		res = (res + mod + a[i] * f * p % mod * inv[x - i] % mod * infact[i] % mod * infact[n - i] % mod) % mod;
	}
	return res;
}

signed main()
{
     
	init(mod);
	scanf("%lld", &t);
	while(t -- ) {
     
		scanf("%lld%lld", &n, &m);
		for(int i = 0; i <= n; ++ i) {
     
			scanf("%lld", &a[i]);
			a[i] %= mod;
		}
		a[n + 1] = lagrange(n + 1, a, n); 
		sum[0] = a[0];
		for(int i = 1; i <= n + 1; ++ i) 
			sum[i] = (sum[i - 1] + a[i]) % mod;
		while(m -- ) {
     
			int l, r;
			scanf("%lld%lld", &l, &r); 
			if(r <= n + 1) {
     
				printf("%lld\n", (sum[r] - sum[l - 1] + mod) % mod);
			}
			else if(l - 1 <= n + 1) {
     
				printf("%lld\n", (lagrange(r, sum, n + 1) - sum[l - 1] + mod) % mod);
			}
			else printf("%lld\n", (lagrange(r, sum, n + 1) - lagrange(l - 1, sum, n + 1) + mod) % mod);
		}
	}
	return 0;
}

D. CF622F The Sum of the k-th Powers(自然数k次幂的和)

Problem

∑ i = 1 n i k \sum_{i=1}^n i^k i=1nik n ≤ 1 0 9 , k ≤ 1 0 6 n \leq 10^9,k \leq 10^6 n109,k106

Solution

n = k + 2 n=k+2 n=k+2

我们知道对于一个 n n n 次多项式 f ( x ) f(x) f(x) ,若有一个数列 f ( 1 ) , f ( 2 ) , f ( 3 ) , . . . f ( n ) f(1),f(2),f(3),...f(n) f(1),f(2),f(3),...f(n),多项式差分为 f ( 2 ) − f ( 1 ) , f ( 3 ) − f ( 2 ) , . . . f(2)-f(1),f(3)-f(2),... f(2)f(1),f(3)f(2),...,显然有结论:多项式差分是关于 i i i n − 1 n-1 n1 次多项式。

题中所给的数列自然数k次幂的和,即 f ( i ) = ∑ j = 1 i j k f(i)=\sum_{j=1}^i j^k f(i)=j=1ijk ,进行多项式差分后得到: Δ f ( i ) = ( i + 1 ) k \Delta f(i)=(i+1)^k Δf(i)=(i+1)k ,是一个关于 i i i k k k 次多项式,可以得出结论:原多项式 f ( n ) f(n) f(n) 是一个关于 n n n k + 1 k+1 k+1 次多项式。

即:自然数k次幂的和是一个 k + 1 k+1 k+1 次多项式。

所以我们只需要预处理出 k + 2 k+2 k+2 个点的值 f ( i ) f(i) f(i) ,就可以使用拉格朗日插值法求出 f ( n ) f(n) f(n)。显然我们可以预处理出 0 ∼ k + 2 0\sim k+2 0k+2 f ( i ) f(i) f(i) 的值,这样得到的是 x x x 取值连续的序列,我们就可以是哟个拉格朗日插值 O ( n ) O(n) O(n) 计算出 f ( n ) f(n) f(n) 的值,即为答案。

Time

O ( k ) O(k) O(k)

Code

#include  
using namespace std;

const int N = 1100007, mod = 1e9 + 7;


int read(){
     
    int s = 0, ne = 1; char c = getchar();
    while(c < '0' || c > '9') {
     if(c == '-') ne = -1; c = getchar();}
    while(c >= '0' && c <= '9') s = (s << 1) + (s << 3) + c - '0', c = getchar();
    return s * ne;
}

int qpow(int a, int b)
{
     
	int res = 1;
	while(b) {
     
		if(b & 1) res = 1ll * res * a % mod;
		a = 1ll * a * a % mod; 
		b >>= 1;
	} 
	return res;
}

int n, k;
int s[N], pre[N], suf[N], infact[N], fact[N], ans;

void init(int k)
{
     
	infact[0] = fact[0] = 1;
	for(int i = 1; i <= k + 10; ++ i)
		fact[i] = 1ll * fact[i - 1] * i % mod;
	infact[k + 10] = qpow(fact[k + 10], mod - 2);
	for(int i = k + 9; i >= 0; -- i)  
		infact[i] = 1ll * infact[i + 1] * (i + 1) % mod;
	infact[0] = 1;
 	
}
 
// i = 1 to n, ∑ i^k

void lagrange(int n, int k)
{
      
	s[0] = 0;
	for(int i = 1; i <= k + 2; ++ i)
 		s[i] = (s[i - 1] + qpow(i, k)) % mod;
	if(n <= k + 2) {
     
		printf("%d", s[n]);
		return ;
	}
	pre[0] = 1;
	for(int i = 1; i <= k + 2; ++ i)
		pre[i] = 1ll * pre[i - 1] * ((n - i + mod) % mod) % mod;
	suf[k + 3] = 1;
	for(int i = k + 2; i; -- i)
		suf[i] = 1ll * suf[i + 1] * ((n - i + mod) % mod) % mod;
		
	for(int i = 1; i <= k + 2; ++ i) {
     
		s[i] = 1ll * s[i] * pre[i - 1] % mod * suf[i + 1] % mod * infact[i - 1] % mod * infact[k + 2 - i] % mod;
		if((k + 2 - i) & 1) 
			ans = (1ll * ans - s[i] + mod) % mod;
		else ans = (1ll * ans + s[i]) % mod;
	}
	printf("%d\n", ans);
}
 
int main()
{
     
	scanf("%d%d", &n, &k);
	init(k);
	lagrange(n, k);
	return 0;
}

E. 牛客挑战赛36 D. 排名估算( “概率论全家桶”,好题,拉格朗日插值求自然数 k 次幂之和)

Weblink

https://ac.nowcoder.com/acm/contest/3782/D

Problem

期末考后,小 C 登录了学校的成绩查询系统,却发现自己的排名被屏蔽了。为了知道自己的排名,小 C 使用了系统中的“好友伴学”功能。每次,系统会在除了小 C 之外的所有考生中随机抽取一名,然后返回 Ta 的排名比小 C 高还是低。这次考试有 n n n 个人参加,小 C 总共使用的 m m m 次 “好友伴学” 功能,却没有一次抽中排名比自己高的人。请问小C在这次考试中的期望排名是多少?

2 ≤ n ≤ 1 0 11 , 0 ≤ m ≤ 5000 2≤n≤10^{11},0≤m≤5000 2n1011,0m5000

假设小C的的期望排名化为最简分数后是 p / q p/q p/q,输出 p ∗ q − 1 m o d    998244353 p*q^{-1}\mod 998244353 pq1mod998244353

Solution

p i p_i pi 表示排名为第 n − i n -i ni 时, m m m 次没抽中排名比自己高的人(排名高指排名在自己前面hhh)的概率。

显然排名只有 1 ∼ n 1\sim n 1n,则对于 0 ≤ i ≤ n − 1 0\le i\le n-1 0in1 p i = ( i n − 1 ) m p_{i}=(\cfrac{i}{n-1})^{m} pi=(n1i)m(排名 n − i n-i ni,则比自己排名高的一共有 n − i n-i ni 个人,没有抽中显然是抽到了自己后面的 i i i 排名低的人)

令事件 A A A m m m 次没抽中,事件 B i B_i Bi 为排名为 n − i n-i ni

根据乘法公式 P ( A B i ) = P ( A ∣ B i ) × P ( B i ) = P ( B i ∣ A ) × P ( A ) P(AB_i)=P(A|B_i)\times P(B_i)=P(B_i|A)\times P(A) P(ABi)=P(ABi)×P(Bi)=P(BiA)×P(A)

我们首先计算 P ( A ) P(A) P(A),根据全概率公式

P ( A ) = ∑ i = 0 n − 1 P ( B i ) × P ( A ∣ B i ) P(A)=\sum\limits_{i=0}^{n-1}P(B_i)\times P(A|B_i) P(A)=i=0n1P(Bi)×P(ABi)

即排名为 i i i 的前提下事件 A A A m m m 次没抽中的概率之和。

显然

P ( B i ) = 1 n , P ( A ∣ B i ) = p i = ( i n − 1 ) m P(B_i)=\frac{1}{n},P(A|B_i)=p_i=(\frac{i}{n-1})^{m} P(Bi)=n1P(ABi)=pi=(n1i)m

P ( A ) = ∑ i = 0 n − 1 ( i n − 1 ) m × 1 n P(A)=\sum_{i=0}^{n-1}(\frac{i}{n-1})^{m} \times \frac{1}{n} P(A)=i=0n1(n1i)m×n1

代入贝叶斯公式

P ( B i ∣ A ) = P ( B i ) × P ( A ∣ B i ) ∑ j = 0 n − 1 P ( B j ) × P ( A ∣ B j ) = p i × 1 n ∑ j = 0 n − 1 p j × 1 n \begin{aligned}P(B_i|A)&=\frac{\displaystyle P(B_i)\times P(A|B_i)}{\displaystyle\sum\limits_{j=0}^{n-1}P(B_j)\times P(A|B_j)}&\\&=\cfrac{p_i\times \dfrac{1}{n}}{\displaystyle\sum_{j=0}^{n-1}p_j\times \frac{1}{n}}\end{aligned} P(BiA)=j=0n1P(Bj)×P(ABj)P(Bi)×P(ABi)=j=0n1pj×n1pi×n1

期望 E ( x ) = P × x E(x)=P\times x E(x)=P×x

x x x 是排名, p p p 为排名为 x x x 时抽 m m m 次抽不到的概率。

显然答案为:
a n s = ∑ x = 1 n E ( x ) = ∑ x = 1 n P ( B i ∣ A ) × ( n − i ) = ∑ i = 0 n − 1 p i × 1 n × ( n − i ) ∑ j = 0 n − 1 p j × 1 n = ∑ i = 0 n − 1 p i × ( n − i ) ∑ i = 0 n − 1 p i = n − ∑ i = 0 n − 1 i m + 1 ∑ i = 0 n − 1 i m \begin{aligned}ans & = \sum_{x=1}^{n}E(x)& \\ & = \sum_{x=1}^{n} P(B_i|A) \times (n - i)&\\&=\sum_{i=0}^{n-1}\cfrac{p_i\times \dfrac{1}{n}\times (n-i)}{\displaystyle\sum_{j=0}^{n-1}p_j\times \frac{1}{n}}&\\&=\displaystyle \frac{\displaystyle \sum_{i=0}^{n-1}p_{i}\times(n-i)}{\displaystyle \sum_{i=0}^{n-1}p_{i}}&\\&=n-\cfrac{\displaystyle\sum_{i=0}^{n-1}i^{m+1}}{\displaystyle\sum_{i=0}^{n-1}i^m}\end{aligned} ans=x=1nE(x)=x=1nP(BiA)×(ni)=i=0n1j=0n1pj×n1pi×n1×(ni)=i=0n1pii=0n1pi×(ni)=ni=0n1imi=0n1im+1

显然答案就是一个自然数 k k k 次幂之和,我们使用拉格朗日插值 O ( n ) O(n) O(n) 计算即可。

特判一下 m = 0 m=0 m=0 的情况,即不抽,那么期望就是:

E ( x ) = P × x = 1 n × ∑ i = 1 n i = 1 n × n ( n + 1 ) 2 = n + 1 2 E(x)=P\times x=\displaystyle\cfrac{1}{n}\times\sum\limits_{i=1}^{n}i=\cfrac{1}{n}\times \cfrac{n(n+1)}{2}=\cfrac{n+1}{2} E(x)=P×x=n1×i=1ni=n1×2n(n+1)=2n+1

当然不需要化简为最简分数,因为除法转换成逆元乘起来是等价的。

Code

// Problem: 排名估算
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/3782/D
// Memory Limit: 524288 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)


#include 
#define int long long
using namespace std;

const int N = 5007, M = 5007, mod = 998244353;
 
int read(){
     
    int s = 0, ne = 1; char c = getchar();
    while(c < '0' || c > '9') {
     if(c == '-') ne = -1; c = getchar();}
    while(c >= '0' && c <= '9') s = (s << 1) + (s << 3) + c - '0', c = getchar();
    return s * ne;
}

int qpow(int a, int b)
{
     
	int res = 1;
	while(b) {
     
		if(b & 1) res = 1ll * res * a % mod;
		a = 1ll * a * a % mod; 
		b >>= 1;
	} 
	return res;
}
 
int s[N], pre[N], suf[N], infact[N], fact[N];

void init(int k)
{
      
	infact[0] = fact[0] = 1;
	for(int i = 1; i <= k + 10; ++ i)
		fact[i] = 1ll * fact[i - 1] * i % mod;
	infact[k + 10] = qpow(fact[k + 10], mod - 2);
	for(int i = k + 9; i >= 0; -- i)  
		infact[i] = 1ll * infact[i + 1] * (i + 1) % mod;
	infact[0] = 1; 
}
 
int lagrange(int n, int k)
{
      
	int ans = 0; 
	s[0] = 0;
 	for(int i = 1; i <= k + 2; ++ i)
 		s[i] = (s[i - 1] + qpow(i, k)) % mod;
	
	if(n <= k + 1)  
		return s[n];  
	
	pre[0] = 1;
	for(int i = 1; i <= k + 2; ++ i)
		pre[i] = 1ll * pre[i - 1] * ((n - i + mod) % mod) % mod;
	suf[k + 3] = 1;
	for(int i = k + 2; i; -- i)
		suf[i] = 1ll * suf[i + 1] * ((n - i + mod) % mod) % mod;
		
	for(int i = 0; i <= k + 2; ++ i) {
     
		s[i] = 1ll * s[i] * pre[i - 1] % mod * suf[i + 1] % mod * infact[i - 1] % mod * infact[k + 2 - i] % mod;
		if((k + 2 - i) & 1) 
			ans = (1ll * ans - s[i] + mod) % mod;
		else ans = (1ll * ans + s[i]) % mod;
	}
	return (ans + mod) % mod;
}
 
int n, m;
 
signed main()
{
     
	init(M - 5);
	scanf("%lld%lld", &n, &m);
	if(m == 0) {
     
		printf("%lld\n", (n + 1) % mod * qpow(2, mod - 2) % mod);
		return 0;
	}
	int up = lagrange(n - 1, m + 1); 
	int down = lagrange(n - 1, m); 
	down = qpow(down, mod - 2);
	printf("%lld\n", (n - up * down % mod + mod) % mod);
}

F. P4593 [TJOI2018]教科书般的亵渎

Weblink

https://www.luogu.com.cn/problem/P4593

Problem

解题报告(三)多项式求值与插值(拉格朗日插值)(ACM / OI)_第4张图片
Solution

炉石6年老玩家报道!

看好了,我将为大家表演一波教科书般的亵渎(&3/-+7%&567%*19&%…+/*-+…_+%*……&&*)(●ˇ∀ˇ●) ————> 扭了,扭了(扭曲虚空)

老师的术士打的还不够多

显然我们需要使用 k = m + 1 k=m+1 k=m+1 张亵渎,那么每使用一张亵渎,获得的贡献就是一段自然数k次幂的和,我们减掉中间缺掉的随从的贡献就行了。

Time

O ( m 2 ) O(m^2) O(m2)

Code

G. P4463 [集训队互测 2012] calc(拉格朗日优化DP)

Weblink

https://www.luogu.com.cn/problem/P4463

Problem

解题报告(三)多项式求值与插值(拉格朗日插值)(ACM / OI)_第5张图片

k ≤ 1 0 9 , n ≤ 500 k≤10 ^9 ,n≤500 k109,n500 p ≤ 1 0 9 p \le 10^9 p109,并且 p p p 为素数, p > k > n + 1 p>k>n+1 p>k>n+1

Solution

显然对于一种取值的合法序列,这个序列不管怎么排列,合法序列的值都一样的,我们先考虑暴力计算,设 d p ( i , j ) dp(i,j) dp(i,j) 表示前 i i i 个数取值域范围 [ 1 , j ] [1,j] [1,j] 的所有取值不同的合法序列的值之和。直接转移很不方便,我们可以只考虑递增的序列,即我们仅需讨论第 i i i 个数取还是不取 j j j

即:

d p [ i ] [ j ] = j ∗ d p [ i − 1 ] [ j − 1 ] + d p [ i ] [ j − 1 ] dp[i][j] = j * dp[i - 1][j - 1] + dp[i][j - 1] dp[i][j]=jdp[i1][j1]+dp[i][j1]

显然答案就是所有取值不同的合法序列的值之和乘上排列的方案数 n ! n! n!。答案就是 d p [ n ] [ k ] dp[n][k] dp[n][k],但是 k ≤ 1 e 9 k\le 1e9 k1e9,考虑优化。

一个DP的递推式可以看作是一个多项式,多项式 f n ( i ) f_n(i) fn(i) 就是 d p [ n ] [ i ] dp[n][i] dp[n][i],那么答案就是 d p [ n ] [ k ] = f n ( k ) dp[n][k] = f_{n}(k) dp[n][k]=fn(k)

代入递推式得:

f i ( j ) − f i ( j − 1 ) = j ∗ f i − 1 ( j − 1 ) f_{i}(j)-f_{i}(j - 1)=j*f_{i - 1}(j - 1) fi(j)fi(j1)=jfi1(j1)

f i ( j ) f_i(j) fi(j) g ( n ) g(n) g(n) 次多项式

前面是一个差分的形式,显然有结论:

  • 两个 n n n 次多项式的差分是一个 n − 1 n-1 n1 次多项式

  • 两个 n n n 次多项式的前缀和是一个 n + 1 n+1 n+1 次多项式

自己代入展开算一下就知道了

f i ( j ) − f i ( j − 1 ) f_i(j)-f_i(j - 1) fi(j)fi(j1) g ( n ) − 1 g(n)-1 g(n)1 次多项式, j ∗ f i − 1 ( j − 1 ) j*f_{i - 1}(j - 1) jfi1(j1) 是一个 g ( n − 1 ) + 1 g(n-1)+1 g(n1)+1 次多项式(乘上了一个 j j j 嘛),即:

g ( n ) − 1 = g ( n − 1 ) + 1 , g ( n ) = g ( n − 1 ) + 2 g(n)-1=g(n-1)+1,g(n)=g(n-1)+2 g(n)1=g(n1)+1g(n)=g(n1)+2

显然 g ( 0 ) = 0 g(0)=0 g(0)=0,则 g ( n ) = 2 × n g(n)=2\times n g(n)=2×n。也就意味着我们只需要计算出 f f f 的前 2 × n + 1 2\times n+1 2×n+1 项的值,就可以唯一确定一个 2 × n 2\times n 2×n 项的多项式,并且因为我们得到的 2 × n + 1 2\times n+1 2×n+1 项的值中的 x x x 还是连续的,也就意味着我们可以用拉格朗日插值法 ,在 O ( 2 ∗ n ) / O ( n 2 ) O(2*n) / O(n^2) O(2n)/O(n2) 的复杂度下直接求出 f n ( k ) f_n(k) fn(k),既是所求的答案。由于需要 O ( n 2 ) O(n^2) O(n2) 预处理 d p dp dp 数组,所以复杂度为 O ( n 2 ) O(n^2) O(n2)

当然本题还有生成函数的做法,利用多项式科技可以做到 O ( n l o g n ) O(nlogn) O(nlogn) :P5850 calc加强版。

Time

O ( n 2 ) O(n^2) O(n2)

Code

 #include 

using namespace std;
#define int long long
const int N = 5007;

int n, m, k, mod;
int dp[N][N];

int qpow(int a, int b)
{
     
	int res = 1;
	while(b) {
     
		if(b & 1) res = res * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return res;
}

int inv(int x)
{
     
	return qpow(x, mod - 2);
}

signed main()
{
     
	scanf("%lld%lld%lld", &k, &n, &mod);
	int m = 2 * n + 1;
	for(int i = 0; i <= m; ++ i)
		dp[0][i] = 1;
	for(int i = 1; i <= n; ++ i) {
     
		for(int j = 1; j <= m; ++ j) {
     
			dp[i][j] = (dp[i][j - 1] + dp[i - 1][j - 1] * j % mod) % mod;
		}
	}

	int ans = 0, fact = 1;
	for(int i = 1; i <= n; ++ i)
		fact = fact * i % mod;
		
	if(k <= m) {
     
		ans = dp[n][k];
		printf("%lld\n", ans * fact % mod);
		return 0;
	}
	for(int i = 1; i <= m; ++ i) {
     
		int up = dp[n][i], down = 1;
		for(int j = 1; j <= m; ++ j) {
     
			if(i != j) {
     
				up = up * (k - j + mod) % mod; 
				down = down * (i - j + mod) % mod;
			}
		}
		ans = (ans + up * inv(down) % mod) % mod;
	} 
	printf("%lld\n", ans * fact % mod);
}

H. CF995F Cowmpany Cowmpensation(拉格朗日优化DP)

Weblink

https://www.luogu.com.cn/problem/CF995F

Problem

%给定n个点m条边的有向无环图,其中没有入度的点被视为源点,没有出度的点被视为汇点。 保证源点和汇点数目相同。 考虑所有把源汇点两两配对,并用两两不相交的路径把它们两两连接起来的所有方案。 如果这个方案中,把源点按标号1到n排序后,得到的对应汇点序列的逆序数对的个数是奇数,那么A给B一块钱,否则B给A一块钱。 问最后A的收益,对大质数取模。 n ≤ 600

树形结构,给每个节点分配工资([1,d]),子节点不能超过父亲节点的工资,问有多少种分配方案

Solution

Code

杜教的代码!%%%

#include 
using namespace std;
#define rep(i,a,n) for (int i=a;i
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef vector<int> VI;
typedef long long ll;
typedef pair<int,int> PII;
const ll mod=1000000007;
ll powmod(ll a,ll b) {
     ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){
     if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll gcd(ll a,ll b) {
      return b?gcd(b,a%b):a;}
// head

namespace polysum {
     
	const int D=101000;
	ll a[D],f[D],g[D],p[D],p1[D],p2[D],b[D],h[D][2],C[D];
	ll calcn(int d,ll *a,ll n) {
     
		if (n<=d) return a[n];
		p1[0]=p2[0]=1;
		rep(i,0,d+1) {
     
			ll t=(n-i+mod)%mod;
			p1[i+1]=p1[i]*t%mod;
		}
		rep(i,0,d+1) {
     
			ll t=(n-d+i+mod)%mod;
			p2[i+1]=p2[i]*t%mod;
		}
		ll ans=0;
		rep(i,0,d+1) {
     
			ll t=g[i]*g[d-i]%mod*p1[i]%mod*p2[d-i]%mod*a[i]%mod;
			if ((d-i)&1) ans=(ans-t+mod)%mod;
			else ans=(ans+t)%mod;
		}
		return ans;
	}
	void init(int M) {
     
		f[0]=f[1]=g[0]=g[1]=1;
		rep(i,2,M+5) f[i]=f[i-1]*i%mod;
		g[M+4]=powmod(f[M+4],mod-2);
		per(i,1,M+4) g[i]=g[i+1]*(i+1)%mod;
	}
	ll polysum(ll n,ll *a,ll m) {
      // a[0].. a[m] \sum_{i=0}^{n-1} a[i]
		a[m+1]=calcn(m,a,m+1);
		rep(i,1,m+2) a[i]=(a[i-1]+a[i])%mod;
		return calcn(m+1,a,n-1);
	}
	ll qpolysum(ll R,ll n,ll *a,ll m) {
      // a[0].. a[m] \sum_{i=0}^{n-1} a[i]*R^i
		if (R==1) return polysum(n,a,m);
		a[m+1]=calcn(m,a,m+1);
		ll r=powmod(R,mod-2),p3=0,p4=0,c,ans;
		h[0][0]=0;h[0][1]=1;
		rep(i,1,m+2) {
     
			h[i][0]=(h[i-1][0]+a[i-1])*r%mod;
			h[i][1]=h[i-1][1]*r%mod;
		}
		rep(i,0,m+2) {
     
			ll t=g[i]*g[m+1-i]%mod;
			if (i&1) p3=((p3-h[i][0]*t)%mod+mod)%mod,p4=((p4-h[i][1]*t)%mod+mod)%mod;
			else p3=(p3+h[i][0]*t)%mod,p4=(p4+h[i][1]*t)%mod;
		}
		c=powmod(p4,mod-2)*(mod-p3)%mod;
		rep(i,0,m+2) h[i][0]=(h[i][0]+h[i][1]*c)%mod;
		rep(i,0,m+2) C[i]=h[i][0];
		ans=(calcn(m,C,n)*powmod(R,n)-c)%mod;
		if (ans<0) ans+=mod;
		return ans;
	}
}

const int N=3010;
int n,d,p,dp[N][N];
VI s[N];
ll pres[N];
void dfs(int u) {
     
	rep(i,0,n+1) dp[u][i]=1;
	for (auto v:s[u]) {
     
		dfs(v);
		rep(i,0,n+1) pres[i]=dp[v][i];
		rep(i,1,n+1) pres[i]=(pres[i]+pres[i-1])%mod;
		rep(i,0,n+1) dp[u][i]=dp[u][i]*pres[i]%mod;
	}
}

int main() {
     
	scanf("%d%d",&n,&d); --d;
	polysum::init(3456);
	rep(i,2,n+1) {
     
		scanf("%d",&p);
		s[p].pb(i);
	}
	dfs(1);
	rep(i,0,n+1) pres[i]=dp[1][i];
	rep(i,1,n+1) pres[i]=(pres[i]+pres[i-1])%mod;
	printf("%lld\n",polysum::calcn(n,pres,d));
}

I. (2018牛客多校(一)F)Sum of Maximum(组合数学+拉格朗日插值)

http://tokitsukaze.live/2018/07/19/2018niuke1.F/

%[https://blog.csdn.net/qq_42819598/article/details/95497117?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-3.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-3.control](https://blog.csdn.net/qq_42819598/article/details/95497117?utm_medium=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromMachineLearnPai2~default-3.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~BlogCommendFromMachineLearnPai2~default-3.control)

多项式求值与插值

A. P5050 【模板】多项式多点求值

Weblink

https://www.luogu.com.cn/problem/P5050

Problem

解题报告(三)多项式求值与插值(拉格朗日插值)(ACM / OI)_第6张图片
Solution

Time

O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

Code

B. P5158 【模板】多项式快速插值

Weblink

https://www.luogu.com.cn/problem/P5158

Problem
解题报告(三)多项式求值与插值(拉格朗日插值)(ACM / OI)_第7张图片

Solution

好长时间没用笔写过字了,怎么这么丑…

Time

O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

Code

%https://blog.csdn.net/a_forever_dream/article/details/112547349

%https://blog.csdn.net/C20190102/article/details/106693455

你可能感兴趣的:(【解题报告】-,超高质量题单,+,题解,多项式,-,求值与插值)