测试地址:轮状病毒
做法:本题需要用到DP+组合数学+高精度。
我们发现题目实际上求的是:将环划分成若干个区间,然后中心点向每个区间连一条边的方案数。我们不妨先考虑链上的情况。令
f(i)
为对一条长为
i
的链进行上述操作的方案数,那么有状态转移方程:
边界条件为
f(0)=1
。上述方程是
O(n2)
的,即使配合高精度运算(
O(n3/log10)
)也够用了,但是其实上述方程可以优化到
O(n)
,由于有点复杂,我们到这篇文章最后再讲。
那么我们再回来考虑环上的情况。令
g(i)
为一个长为
i
的环上进行上述操作的方案数,考虑固定某一点,枚举这一个点所处的区间长度
g(i)=∑ij=1j2f(i−j)
那么最后的答案就是
g(n)
。因为题目要求准确的答案,需要高精度辅助,时间复杂度为
O(n3/log10)
。
其实上述复杂度已经够用了,但是我们不满足于现状,我们硬是要找出规律,那么以下是我找出的规律:
f(n)=Fib(2n)(n>0)
,其中
Fib(i)
为斐波那契数列的第
i
项,这里令递推式为
怎么证明呢?用数学归纳法即可。如果看不下去数学证明的可以跳过,但是代码是用以上推出的公式写的……
当
n=1
时,结论显然成立。
当
n>1
时,假设对于任意
k<n
,
f(k)=Fib(2k)
成立,那么:
∑ni=1i×f(n−i)
=∑ni=1∑i−1j=0f(j)
=1+∑ni=2[1+∑i−1j=1Fib(2j)]
=1+∑ni=2{1+∑i−1j=1[Fib(2j−1)+Fib(2j−2)]}
=1+∑ni=2[1+∑2i−3j=1Fib(j)]
我们此时引用一个引理:
∑ni=1Fib(n)=Fib(n+2)−1
,之后证明。那么上式:
=1+∑ni=2Fib(2i−1)
=1+∑ni=2[Fib(2i−2)+Fib(2i−3)]
=1+∑2n−2i=1Fib(i)
=1+Fib(2n)−1
=Fib(2n)
因此对于任意自然数
n>0
,有
f(n)=Fib(2n)
。
现在证明引理
∑ni=1Fib(i)=Fib(n+2)−1
,同样数学归纳:
当
n=1
时,结论显然成立。
当
n>1
时,假设对于任意
k<n
,上述结论对
k
成立,那么:
=Fib(n)+∑n−1i=1Fib(i)
=Fib(n)+Fib(n+1)−1
=Fib(n+2)−1
因此对于任意自然数
n>0
,上述结论成立。
那么求
f(n)
就可以规约为求斐波那契数列了,那么优化后的算法复杂度为
O(n2/log10)
。
以下是本人代码:
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n;
struct hd
{
int s[110];
void output()
{
bool flag=0;
for(int i=100;i>=1;i--)
{
if (s[i]) flag=1;
if (flag) printf("%d",s[i]);
}
}
} f[210],ans;
hd pl(hd a,hd b)
{
hd q;
for(int i=1;i<=100;i++) q.s[i]=a.s[i]+b.s[i];
for(int i=1;i<=100;i++)
if (q.s[i]>=10) q.s[i+1]+=q.s[i]/10,q.s[i]%=10;
return q;
}
hd mult(hd a,int b)
{
hd q;
for(int i=1;i<=100;i++) q.s[i]=a.s[i]*b;
for(int i=1;i<=100;i++)
if (q.s[i]>=10) q.s[i+1]+=q.s[i]/10,q.s[i]%=10;
return q;
}
int main()
{
for(int i=1;i<=100;i++)
{
for(int j=1;j<=200;j++) f[j].s[i]=0;
ans.s[i]=0;
}
scanf("%d",&n);
f[0].s[1]=f[1].s[1]=f[2].s[1]=1;
for(int i=3;i<=2*n;i++)
f[i]=pl(f[i-1],f[i-2]);
for(int i=1;i<=n;i++)
ans=pl(ans,mult(f[2*(n-i)],i*i));
ans.output();
return 0;
}