拉格朗日插值优化DP

拉格朗日插值优化DP

由于博客园挂了来CSDN更新一发。
我的博客园:DCH233

模拟赛出现神秘插值,太难啦!!

首先回忆拉格朗日插值是用来做什么的

对于一个多项式 F ( x ) F(x) F(x),如果已知它的次数为 m − 1 m - 1 m1,且已知 m m m个点值,那么可以得到

F ( k ) = ∑ i = 1 m y i ∏ j ≠ i k − x j x i − x j F(k) = \sum_{i=1}^{m} y_i \prod_{j \neq i} \frac{k-x_j}{x_i - x_j} F(k)=i=1myij=ixixjkxj

所以,如果我们知道要求的东西是一个次数比较友好的多项式且容易求出一些点值,那么就可以把答案插出来。

来看两道例题

CF995F Cowmpany Cowmpensation

题意:给你一棵树,要求给每个点分配 [ 1 , d ] [1,d] [1,d]内的权值,且儿子的权值不能超过父亲的权值,对 1 0 9 + 7 10^9+7 109+7取模, D ≤ 1 0 9 D\leq 10^9 D109

很容易得到一个 DP \text{DP} DP,设 f u , i f_{u,i} fu,i表示u子树内u的权值大于等于 i i i的答案,那么
f u , i = ∏ v f v , i + f u , i + 1 f_{u,i} = \prod_v f_{v,i} + f_{u,i + 1} fu,i=vfv,i+fu,i+1

但是 i i i的值域是 [ 1 , D ] [1,D] [1,D],根本做不了,怎么办?

拉格朗日插值登场。

假设 u u u是一个叶子结点,那么 f u , i = D − i + 1 f_{u,i} = D - i + 1 fu,i=Di+1是一个关于 i i i的一次多项式

由于转移方程是简单的乘法和加法的形式,可以看出来 f u , i f_{u,i} fu,i就是一个关于 i i i的多项式,到这里我们需要考虑的就是这个多项式的次数是多少。

g u g_u gu表示 f u , i f_{u,i} fu,i的次数,那么根据上面的状态转移方程,可以得到

f u , i − f u , i + 1 = ∏ v f v , i f_{u,i} - f_{u, i + 1} = \prod_v f_{v,i} fu,ifu,i+1=vfv,i

根据多项式基础知识,一个多项式差分,次数减一;多个多项式相乘,子树相加,那么就有

g u − 1 = ∑ v g v ⇒ g u = s z u g_u - 1 = \sum_v g_v \Rightarrow g_u = sz_u gu1=vgvgu=szu

这里 s z u sz_u szu表示 u u u子树的大小

所以答案就是一个关于 d d d n n n次多项式,求出 n + 1 n+1 n+1个点值后即可使用拉格朗日插值得到答案。

#include <cstdio>
#include <vector>
#include <iostream>
#define LL long long
using namespace std;
template <typename T>
inline void read(T &x) {
	x = 0; int f = 0; char ch = getchar();
	for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
	for(; isdigit(ch); ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);
	if(f) x = ~x + 1;
}
const int N = 3010;
const LL P = 1e9 + 7;
int n, d, m;
int f[N][N << 1];
int y[N << 1];
vector <int> G[N];
void dfs(int u) {
	for(int i = 1; i <= m; ++i) f[u][i] = 1;
	for(auto v : G[u]) {
		dfs(v);
		for(int i = m; i ; --i)
			f[u][i] = 1ll * f[u][i] * f[v][i] % P;
	}
	for(int i = m - 1; i ; --i) f[u][i] = (f[u][i] + f[u][i + 1]) % P;
}
LL fpow(LL x, int pnt = P - 2) {
	LL res = 1;
	for(; pnt; pnt >>= 1, x = x * x % P) if(pnt & 1) res = res * x % P;
	return res;
}
int Lagrange(int x) {
	if(1 <= x && x <= m) return y[x];
	LL res = 0;
	for(int i = 1; i <= m; ++i) {
		LL p = y[i], q = 1;
		for(int j = 1; j <= m; ++j) 
			if(i ^ j) p = p * (x - j) % P, q = q * (i - j) % P;
		res = (res + p * fpow(q)) % P;
	}
	return res;
}
int main() {
	read(n), read(d);
	for(int i = 2, u; i <= n; ++i) {
		read(u);
		G[u].emplace_back(i);
	}
	m = n + 1;
	dfs(1);
	for(int i = 1; i <= m; ++i) y[m - i + 1] = f[1][i];
	printf("%d\n",Lagrange(d));
}

[集训队互测 2012] calc

经典题

DP \text{DP} DP还是很容易,首先由于互不相等,先转化成 a i a_i ai有序,然后设 f i , j f_{i,j} fi,j表示已经填了 i i i个数,值域为 [ 1 , j ] [1,j] [1,j],转移方程就是

f i , j = j f i − 1 , j − 1 + f i , j − 1 f_{i,j} = jf_{i - 1,j - 1} + f_{i, j - 1} fi,j=jfi1,j1+fi,j1

按照上面的方法,设 g i g_i gi为关于 j j j的多项式 f i , j f_{i,j} fi,j的次数,那么有

g i − 1 = g i + 1 ⇒ g i = 2 i g_i - 1 = g_i + 1 \Rightarrow g_i = 2i gi1=gi+1gi=2i

然后 f n , i f_{n,i} fn,i的次数就是 2 n 2n 2n,求 2 n + 1 2n+1 2n+1个点就能把答案插出来了

#include <cstdio>
#include <iostream>
#define LL long long
using namespace std;
template <typename T>
inline void read(T &x) {
	x = 0; int f = 0; char ch = getchar();
	for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
	for(; isdigit(ch); ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);
	if(f) x = ~x + 1;
}
const int N = 510;
int k, n, m;
LL P, y[N << 1], f[N][N << 1];
LL fpow(LL x, int pnt = P - 2) {
	LL res = 1;
	for(; pnt; pnt >>= 1, x = x * x % P) if(pnt & 1) res = res * x % P;
	return res;
}
LL Lagrange(int x) {
	if(1 <= x && x <= m) return y[x];
	LL res = 0;
	for(int i = 1; i <= m; ++i) {
		LL p = y[i], q = 1;
		for(int j = 1; j <= m; ++j) 
			if(j != i) p = p * (k - j) % P, q = q * (i - j) % P;
		if(p < 0) p += P; if(q < 0) q += P;
		res = (res + p * fpow(q)) % P;
	}
	return res;
}
int main() {
	read(k), read(n), read(P), m = (n << 1) + 1;
	LL fac = 1; for(int i = 1; i <= n; ++i) fac = fac * i % P;
	for(int i = 0; i <= m; ++i) f[0][i] = 1;
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= m; ++j)
			f[i][j] = (f[i - 1][j - 1] * j + f[i][j - 1]) % P;
	for(int i = 1; i <= m; ++i) y[i] = f[n][i];
	printf("%d\n",fac * Lagrange(k) % P);
}

总结

拉格朗日插值优化 DP \text{DP} DP是一种优化思路,在值域比较大,容易求点值的时候可以考虑下,上面给出了例子比较简单,需要在遇到具体问题时具体考虑。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值