bzoj 2655: calc

来源:互联网 发布:sublime php错误提示 编辑:程序博客网 时间:2024/05/29 15:00

题目传送门: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[ij][2a+1][2b+2a])

复杂度:O(n2logA+n2logA)

期望得分:70—100

算法五:

考虑暴力dp

设f[i]表示i个数时的答案

转移?

容斥原理

无限制乘积 - 至少有一个重复的 + 至少有两个重复的 - ····

f[i]=g[1]f[i1]+((1)ij+1f[j]C(i1,i1j)(i1j)!g[ij])

O(n2)预处理伯努利数g[i]=Aj=1ji

复杂度:O(n2)

期望得分:100分

ps:一开始组合数写错了,身败名裂

其实还有一种有关多项式的做法:

xiaoyimi大爷的做法:
从原始dp的思路出发
f[i][j]表示i个元素中选取j个的分数
写出dp方程
f[i][j]=f[i1][j1]×i×j+f[i1][j]
初始状态f[0][0]=1
手玩j比较小的情况,容易发现,f[i][j]实际上是一个最高次项为2j的多项式
那也就是说我们最终的答案f[A][n]就是一个2n次的多项式
这是个很好的性质,因为我们只用求出02n次项的系数就可以直接求答案了
那这个系数怎么求呢?
有一种比较容易想到的方法是求出较小的f[i][n],然后高斯消元
但这样的复杂度是O(n3),对这道题来说很不优美
所以我们尝试引入拉格朗日插值法
f(x)是一个n次多项式,则有

f(x)=i=0naixi=i=0nf(xi)j=0nxxjxixj[ij]

其中x1,x2,..xn互不相等
同样是求出2nf[i][n]然后求解f[A][n],但是显然这个复杂度比高斯消元低多了
理论复杂度为O(n2logA),因为还要求解逆元
但实际上是可以做到O(n2)的,因为选取的点都是0,1,2,3,4..2n,随便求和预处理一下就可以O(nlogA)了,主要复杂度在初始DP那里,但是常数催人泪下……

容斥原理:

#include<iostream>#include<cstdio>#include<cstring>#include<string>#define N 505using 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.推导的时候要仔细,不能胡乱推啊,不要总是自以为什么,要考虑式子的意义,从多方面考虑

0 0
原创粉丝点击