题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2655
思路:
算法一:
A<=10,n<=10
注意到n和A都很小直接枚举每一位是什么就行了
时间复杂度: O(n!)
期望得分:5
算法二:
n和A都不太大,考虑dp
注意到我们可以将一个合法解排序,只需计算严格递增序列的答案最后乘上n!即可
f[i][j] 表示dp到i且i选的数是j得到的值
期望得分:20
算法三:
我们发现A很大,相比之下n是很小的
考虑倍增优化算法二
设 f[n][a][b] 表示n个数,数的范围在a到b的ans
预处理组合数等,很容易 O(n) 转移到 f[n][a+k][b+k] ,注意这里要算一些组合数,它们的上标范围是值域而不是长度
倍增求出 f[i][1][2k] ,将A二进制分解,考虑如何合并
f[i][1][2a+2b]=∑(f[j][1][2a]∗f[i−j][2a+1][2b+2a])
复杂度: O(n2logA+n2logA)
期望得分:70—100
算法五:
考虑暴力dp
设f[i]表示i个数时的答案
转移?
容斥原理
无限制乘积 - 至少有一个重复的 + 至少有两个重复的 - ····
f[i]=g[1]∗f[i−1]+∑((−1)i−j+1∗f[j]∗C(i−1,i−1−j)∗(i−1−j)!∗g[i−j])
O(n2) 预处理伯努利数 g[i]=∑Aj=1ji
复杂度: O(n2)
期望得分:100分
ps:一开始组合数写错了,身败名裂
其实还有一种有关多项式的做法:
xiaoyimi大爷的做法:
从原始dp的思路出发
f[i][j]表示i个元素中选取j个的分数
写出dp方程
f[i][j]=f[i−1][j−1]×i×j+f[i−1][j]
初始状态
f[0][0]=1
手玩j比较小的情况,容易发现,
f[i][j]
实际上是一个最高次项为
2j
的多项式
那也就是说我们最终的答案
f[A][n]
就是一个
2n
次的多项式
这是个很好的性质,因为我们只用求出
0
到
那这个系数怎么求呢?
有一种比较容易想到的方法是求出较小的
f[i][n]
,然后高斯消元
但这样的复杂度是
O(n3)
,对这道题来说很不优美
所以我们尝试引入拉格朗日插值法
设
f(x)
是一个
n
次多项式,则有
其中 x1,x2,..xn 互不相等
同样是求出 2n 个 f[i][n] 然后求解 f[A][n] ,但是显然这个复杂度比高斯消元低多了
理论复杂度为 O(n2logA) ,因为还要求解逆元
但实际上是可以做到 O(n2) 的,因为选取的点都是 0 ,
容斥原理:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#define N 505
using namespace std;
typedef long long LL;
LL A,n,P,g[N + 5],f[N + 5],sum[N + 5],c[N + 5][N + 5],fact[N + 5],inv[N + 5];
int main(){
//freopen("calc20.in","r",stdin);
//freopen("calc20.out","w",stdout);
cin>>A>>n>>P;
memset(f,0,sizeof(f));
f[0] = 1; f[1] = (A * (A + 1)>>1) % P;
inv[1] = 1;
for (int i = 2;i <= n + 5; ++i) inv[i] = (P - P/i) * inv[P % i] % P;
fact[0] = 1;
for (int i = 1;i <= n + 5; ++i) fact[i] = i * fact[i - 1] % P;
for (int i = 0;i <= n + 5; ++i) c[i][0] = 1;
for (int i = 1;i <= n + 5; ++i)
for (int j = 1;j <= i; ++j)
c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % P;
g[0] = (A + 1)%P; g[1] = (A * (A + 1)>>1) % P;
LL pow = (A + 1) * (A + 1) % P;
for (int i = 2;i <= n; ++i){
pow = pow * (A + 1) % P;
LL sum = (A + 1) % P;
for (int j = 1;j < i; ++j) sum = (sum + c[i + 1][j] * g[j] % P) % P;
sum = (pow - sum)%P;
if (sum < 0) sum += P;
g[i] = inv[i + 1] * sum % P;
}
for (int i = 2;i <= n; ++i){
f[i] = g[1] * f[i - 1] % P;
LL op = -1;
for (int j = i - 2;j >= 0; --j){
f[i] = (f[i] + op * fact[i - 1 - j] % P * c[i - 1][i - 1 - j] % P * g[i - j] % P * f[j] % P + P) % P;
op = -op;}
}
cout<<f[n];
// fclose(stdin); fclose(stdout);
return 0;
}
1.推导的时候要仔细,不能胡乱推啊,不要总是自以为什么,要考虑式子的意义,从多方面考虑