[FJOI2007] 轮状病毒

由于内容过长、公式较多,暂时将内容隐藏,请公式恐惧症们做好心理准备。

题解备份

Problem

Description

轮状病毒有很多变种,所有轮状病毒的变种都是从一个轮状基产生的。

一个 N 轮状基由圆环上 N 个不同的基原子和圆心处一个核原子构成的,2 个原子之间的边表示这 2 个原子之间的信息通道。

如下图所示

1.png

N 轮状病毒的产生规律是在一个 N 轮状基中删去若干条边,使得各原子之间有唯一的信息通道,例如共有 16 个不同的 3 轮状病毒,如下图所示:

2.png

现给定 N(N ≤100),编程计算有多少个不同的 N 轮状病毒

Input

第一行有 1 个正整数 N

Output

计算出的不同的 N 轮状病毒数输出

Sample Input

3

Sample Output

16

法一: 行列式

转载自 vfleaking,

对于新手还是建议去看看基尔霍夫矩阵,这一篇论文挺不错的。

用基尔霍夫矩阵使用高斯消元解行列式,时间复杂度 O(n3) 似乎可以 AC。

首先行列式有很多性质:

  • 第 a 行 ×k 加到第 b 行上去,行列式的值不变
  • 三角行列式的值等于对角线元素之积
  • 第 a 行与第 b 行互换,行列式的值取反
  • 常数 × 行列式,可以把常数乘到某一行里去

如果你行列式不是很熟,建议先搜搜行列式~不然下面会看晕~

其实如果你仔细观察矩阵,可以发现它是这样的:(消去了病毒中央)

|3−100⋯000−1−13−10⋯00000−13−1⋯000000−13⋯0000000−1⋯0000⋮⋮⋮⋮⋱⋮⋮⋮⋮0000⋯3−1000000⋯−13−100000⋯0−13−1−1000⋯00−13|

那么我们现在对行列式进行变换,我们把第 1 行与第 2 行交换,再把第 2 行与第 3 行交换……,再把第 n−1 行与第 n 行变换,得到新的行列式:

|−13−10⋯00000−13−1⋯000000−13⋯0000000−1⋯0000⋮⋮⋮⋮⋱⋮⋮⋮⋮0000⋯3−1000000⋯−13−100000⋯0−13−1−1000⋯00−133−100⋯000−1|

这个行列式跟一开始的那个行列式的值不一定相等。因为我们是通过 n−1 次交换行的操作得到的,为了说话方便我们称一开始的行列式为 A,上面刚写的行列式为 B 那么由行列式性质得:A=(−1)n−1⋅B 现在就可以正大光明地处理 B 了~

利用行列式性质,来手算这个行列式。之所以刚才有那么一步,就是为了方便手算。因为观察 B 矩阵,发现就只剩下左下角的 −1、3、−1 三个倒霉了。

倒数第二行:−1000⋯00−13用第一行的:−13−10⋯0000乘以−1 来消:0−310⋯00−13再用第二行:0−13−1⋯0000乘以−3 来消:00−83⋯00−13

这样就有了初步感觉了~

现在把这个过程一般化:

第 k 个和第 k+1 个:00⋯F(k)G(k)00⋯−13总能找到上面的某一行00⋯−13   −10⋯00乘以 F(k) 来消:00⋯0F(k+1)G(k+1)0⋯−13

于是得到:

{F(k+1)=G(k)+3F(k)G(k+1)=−F(k)

整合一下:F(k+1)=3F(k)−F(k−1)

从初始的行和消了一次之后的行中取得边界条件:F(1)=−1,F(2)=−3 最终一定会变为下面这种情况:

倒数第二行:0000⋯F(n−3)G(n−3)−13用倒数第四行:0000⋯−13−10乘以 F(n−3) 来消:0000⋯0F(n−2)G(n−2)−13用倒数第三行:0000⋯0−13−1乘以 F(n−2) 来消:0000⋯00F(n−1)−1G(n−1)+3

好现在搞定了倒数第二行,来看看成果:(f=F(n−1)−1,g=G(n−1)+3)

|−13−10⋯00000−13−1⋯000000−13⋯0000000−1⋯0000⋮⋮⋮⋮⋱⋮⋮⋮⋮0000⋯3−1000000⋯−13−100000⋯0−13−10000⋯00fg3−100⋯000−1|

好,现在来搞倒数第一行。和倒数第二行的方法是类似的。

再设函数 H(k) 和 I(k),意义与 F(k)、G(k) 类似,得:

{H(k+1)=I(k)+3H(k)I(k+1)=−H(k)

其实跟 F、G的递推式是一样的我会乱说?H(k+1)=3H(k)−H(k−1) 边界条件是:H(1)=3,H(2)=8 最后使劲搞一搞,倒数第一行就成了:

00000⋯00H(n−1)I(n−1)−1

再来看成果:(h=H(n−1),i=I(n−1)−1)

|−13−10⋯00000−13−1⋯000000−13⋯0000000−1⋯0000⋮⋮⋮⋮⋱⋮⋮⋮⋮0000⋯3−1000000⋯−13−100000⋯0−13−10000⋯00fg0000⋯00hi|

用倒数第二行来消倒数第一行,得:

0000⋯000i−g⋅(hf)

现在这个行列式已经是三角行列式了,它的值就是对角线元素之积。于是:B=(−1)×(−1)×(−1)×⋯×f(i−g⋅hf) 一共有 n−2 个 −1。

如前文所述:A=(−1)n−1B

又因为:B=(−1)n−2(f⋅i−g⋅h)

于是有:A=(−1)2n−3(f⋅i−g⋅h)=−f⋅i+g⋅h

带入 f、g、h、i 的值得:A=−(F(n−1)−1)(I(n−1)−1)+(G(n−1)+3)H(n−1)

带入 H、I 的值:A=−(F(n−1)−1)(−H(n−2)−1)+(−F(n−2)+3)H(n−1)

然后再展开…… 回忆下 F、H 的递推式

A=F(n−1)H(n−2)+F(n−1)−H(n−2)−1−F(n−2)H(n−1)+3H(n−1)=H(n)+F(n−1)+F(n−1)H(n−2)−F(n−2)H(n−1)−1=H(n)+F(n−1)+|F(n−1)H(n−1)F(n−2)H(n−2)|−1

发现不能化简了?没关系!在行列式上动动手脚吧!

FH 定理

对于任意大于 2 的 k 有:

|F(k−1)H(k−1)F(k−2)H(k−2)|=|F(2)H(2)F(1)H(1)|

证明:对于行列式:

|F(k−1)H(k−1)F(k−2)H(k−2)|

把行列式最下面的行取反,则行列式的值取反:

−|F(k−1)H(k−1)−F(k−2)−H(k−2)|

把行列式的上面的行乘以 3 加到下面去:

−|F(k−1)H(k−1)3F(k−1)−F(k−2)3H(k−1)−H(k−2)|

特意构造的递推式出现了:

−|F(k−1)H(k−1)F(k)H(k)|

有点眉目了~ 把第一行与第二行调换位置,行列式的值取反:

|F(k)H(k)F(k−1)H(k−1)|

一目了然,这是 k++ 后的行列式的样子。(Pascal 同学早日转 C++)

那么立即推出:

|F(k−1)H(k−1)F(k−2)H(k−2)|=|F(2)H(2)F(1)H(1)|

FH 定理得证。

利用 FH 定理,把 F(1)=−1,F(2)=−3,H(1)=3,H(2)=8 带入:

|F(n−1)H(n−1)F(n−2)H(n−2)|=−1

于是就爽了嘛!

∴A=H(n)+F(n−1)+(−1)−1=H(n)+F(n−1)−2

进一步我们发现…… 设 R(n)=H(n)+F(n−1)−2

那么立即有:

R(n)=3H(n−1)−H(n−2)+3F(n−2)−F(n−3)−2=3(R(n−1)+2)−(R(n−2)+2)−2=3R(n−1)−R(n−2)+2

所以,轮状病毒的方案数满足递推式 F(n)=3F(n−1)−F(n−2)+2,其中 F(1)=1,F(2)=5。

然后随手写一个高精度就可以过了~

法二:DP

转载自 boshi

如果用 f[x] 表示加入了 x 个周围的点后的方案数, 我们首先想到的递推式是:f[i]=∑j=1if[i−j]⋅j

解释:最后加入的 j 个点每个都可能与中心点连边,将所有方案数累加即可。

但是,第一个点永远不会与第 n 个点连边,因此方案数统计并不准确。

我们再设:g[i]=∑j=2if[i−j]⋅j⋅(j−1)

解释:如果有 j 个周围的点连成一条,且跨越了 1 和 n,我们将所有这样的情况累加到答案中去。如果这样的点有 j 个,剩下的点肯定不与这 j 个点相连,所以连边方案数就是 f[i−j],这 j 个点有 (j−1) 种选法(跨越 1 和 n),与中心点连边的方案数是 j,根据乘法原理,答案要累加 f[i−j]⋅j⋅(j−1)。

这样的 f[n]+g[n] 就是我们要求的轮状病毒的数量。

下面我们思考如何快速求出 f 和 g。

多阶差分

首先分析 f[i]。如果我们可以求出所有 f[i−j]⋅j 的前缀和,这个问题就变得非常方便了。

问题是对于不同的 i,这个前缀和中每一项都会发生变化。

那如果我们知道了变化的量是多少呢?于是我们就对前缀和进行差分。

Δf[i]=∑j=1if[i−j]⋅j−∑j=1i−1f[i−1−j]⋅j=∑j=0if[i−j]⋅j−∑j=0i−1f[i−1−j]⋅j(f[i]⋅0=f[i−1]⋅0=0)=∑j=0if[j]⋅(i−j)−∑j=0i−1f[j]⋅(i−1−j)(交换枚举顺序)=∑j=0if[i]g[i]=∑j=2if[i−j]⋅j⋅(j−1)=∑j=0if[i−j]⋅j⋅(j−1)(f[i−1]⋅1⋅0=0)Δg[i]=∑j=0if[i−j]⋅j⋅(j−1)−∑j=0i−1f[i−j−1]⋅(j−1)⋅(j−2)=∑j=0if[j]⋅(i−j)⋅(i−j+1)−∑j=0i−1f[j]⋅(i−j)⋅(i−j−1)=∑j=0if[j]⋅(i−j)⋅2(i−i=0)Δ2g[i]=∑j=0if[j]⋅(i−j)⋅2−∑j=0i−1f[j]⋅(i−j−1)⋅2=∑j=0i2f[j](i−i=0)∴Δ3=2f[i]

我们维护 f[i] 的前缀和,以及 f[i−j]⋅j 的前缀和,每次将 f[i] 累加进 f[i] 的前缀和,将 f[i] 的前缀和累加进 f[i−j]⋅j 的前缀和,g[i] 同理。

行列式解法:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define digit 100000000
using namespace std;
struct bigint
{
mutable int a[205];
inline bigint()
{
memset(a,0,sizeof(a));
a[0]=1;a[1]=0;return;
}
inline bigint(int b)
{
memset(a,0,sizeof(a));
a[0]=1;a[1]=b;return;
}
inline int& operator[](size_t pos)const{return a[pos];}
inline bigint operator+(int b)const
{
int i;bigint c=*this;
c[1]+=b;
for(i=1;i<=c[0];++i)
{
if(c[i]>=digit)
{
++c[i+1];c[i]-=digit;
}
}
if(c[c[0]+1])++c[0];
return c;
}
inline bigint operator-(const bigint& b)const
{
int i;bigint c;c[0]=a[0];
for(i=1;i<=c[0];++i)c[i]=a[i]-b[i];
for(i=1;i<=c[0];++i)
{
if(c[i]<0)
{
c[i]+=digit;--c[i+1];
}
}
while(!c[c[0]]&&c[0]>1)--c[0];
return c;
}
inline bigint operator*(int b)const
{
int i;bigint c;c[0]=a[0];
for(i=1;i<=c[0];++i)c[i]=a[i]*b;
for(i=1;i<=c[0]||c[i];++i)
{
if(c[i]>=digit)
{
c[i+1]+=c[i]/digit;c[i]%=digit;
}
}
c[0]=i-1;
return c;
}
friend ostream& operator<<(ostream& os,const bigint& a)
{
printf("%d",a[a[0]]);
for(int i=a[0]-1;i>=1;--i)printf("%08d",a[i]);
return os;
}
}f[3005];
int main(void)
{
int i,n;
scanf("%d",&n);
f[1]=1;f[2]=5;
for(i=3;i<=n;++i)f[i]=f[i-1]*3-f[i-2]+2;
cout<<f[n]<<endl;
return 0;
}

DP 解法:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define digit 1000000000
using namespace std;
struct bigint
{
mutable int a[205];
inline bigint()
{
memset(a,0,sizeof(a));
a[0]=1;a[1]=0;return;
}
inline bigint(int b)
{
memset(a,0,sizeof(a));
a[0]=1;a[1]=b;return;
}
inline int& operator[](size_t pos)const{return a[pos];}
inline bigint& operator+=(const bigint& b)
{
int i;a[0]=max(a[0],b[0]);
for(i=1;i<=a[0];++i)a[i]+=b[i];
for(i=1;i<=a[0];++i)
{
if(a[i]>=digit)
{
++a[i+1];a[i]-=digit;
}
}
if(a[a[0]+1])++a[0];
return *this;
}
friend ostream& operator<<(ostream& os,const bigint& a)
{
printf("%d",a[a[0]]);
for(int i=a[0]-1;i>=1;--i)printf("%09d",a[i]);
return os;
}
}F=1,F1=1,F2=1,G=0,G1=0,G2=0;
int main(void)
{
int i,n;
scanf("%d",&n);
for(i=1;i<=n;++i)
{
if(i)G2+=F,G2+=F;
if(i<n)G1+=G2,G+=G1;
F=F1;F2+=F;F1+=F2;
}
cout<<(F+=G)<<endl;
return 0;
}

完结撒花!★,°:.☆( ̄▽ ̄)/$:.°★