[FJOI2007] 轮状病毒

lolifamily

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

Problem

Description

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

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

如下图所示

1.png

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

2.png

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

Input

第一行有1个正整数𝑁

Output

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

Sample Input

3

Sample Output

16

法一:行列式

转载自 vfleaking题解备份

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

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

首先行列式有很多性质:

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

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

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

∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣310000011310000001310000001300000001000000003100000013100000013110000013∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣

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

∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣131000000131000000130000000100000000310000001310000001311000001331000001∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣

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

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

倒数第二行:10000013用第一行的:13100000乘以1 来消:03100013再用第二行:01310000乘以3 来消:00830013

这样就有了初步感觉了~

现在把这个过程一般化:

 𝑘 个和第 𝑘+1 个:00𝐹(𝑘)𝐺(𝑘)0013总能找到上面的某一行0013   1000乘以 𝐹(𝑘) 来消:000𝐹(𝑘+1)𝐺(𝑘+1)013

于是得到:

{𝐹(𝑘+1)=𝐺(𝑘)+3𝐹(𝑘)𝐺(𝑘+1)=𝐹(𝑘)

整合一下:𝐹(𝑘+1)=3𝐹(𝑘)𝐹(𝑘1)

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

倒数第二行:0000𝐹(𝑛3)𝐺(𝑛3)13用倒数第四行:00001310乘以 𝐹(𝑛3) 来消:00000𝐹(𝑛2)𝐺(𝑛2)13用倒数第三行:00000131乘以 𝐹(𝑛2) 来消:000000𝐹(𝑛1)1𝐺(𝑛1)+3

好现在搞定了倒数第二行,来看看成果:(𝑓=𝐹(𝑛1)1,𝑔=𝐺(𝑛1)+3)

∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣13100000013100000013000000010000000031000000131000000131000000𝑓𝑔31000001∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣

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

再设函数𝐻(𝑘)𝐼(𝑘),意义与𝐹(𝑘)𝐺(𝑘)类似,得:

{𝐻(𝑘+1)=𝐼(𝑘)+3𝐻(𝑘)𝐼(𝑘+1)=𝐻(𝑘)

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

0000000𝐻(𝑛1)𝐼(𝑛1)1

再来看成果:(=𝐻(𝑛1),𝑖=𝐼(𝑛1)1)

∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣13100000013100000013000000010000000031000000131000000131000000𝑓𝑔000000𝑖∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣ ∣

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

0000000𝑖𝑔(𝑓)

现在这个行列式已经是三角行列式了,它的值就是对角线元素之积。于是:𝐵=(1)×(1)×(1)××𝑓(𝑖𝑔𝑓)一共有𝑛21

如前文所述:𝐴=(1)𝑛1𝐵

又因为:𝐵=(1)𝑛2(𝑓𝑖𝑔)

于是有:𝐴=(1)2𝑛3(𝑓𝑖𝑔)=𝑓𝑖+𝑔

带入𝑓𝑔𝑖的值得:𝐴=(𝐹(𝑛1)1)(𝐼(𝑛1)1)+(𝐺(𝑛1)+3)𝐻(𝑛1)

带入𝐻𝐼的值:𝐴=(𝐹(𝑛1)1)(𝐻(𝑛2)1)+(𝐹(𝑛2)+3)𝐻(𝑛1)

然后再展开…… 回忆下𝐹𝐻的递推式

𝐴=𝐹(𝑛1)𝐻(𝑛2)+𝐹(𝑛1)𝐻(𝑛2)1𝐹(𝑛2)𝐻(𝑛1)+3𝐻(𝑛1)=𝐻(𝑛)+𝐹(𝑛1)+𝐹(𝑛1)𝐻(𝑛2)𝐹(𝑛2)𝐻(𝑛1)1=𝐻(𝑛)+𝐹(𝑛1)+𝐹(𝑛1)𝐻(𝑛1)𝐹(𝑛2)𝐻(𝑛2)1

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

FH 定理

对于任意大于2𝑘有:

𝐹(𝑘1)𝐻(𝑘1)𝐹(𝑘2)𝐻(𝑘2)=𝐹(2)𝐻(2)𝐹(1)𝐻(1)

证明:对于行列式:

𝐹(𝑘1)𝐻(𝑘1)𝐹(𝑘2)𝐻(𝑘2)

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

𝐹(𝑘1)𝐻(𝑘1)𝐹(𝑘2)𝐻(𝑘2)

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

𝐹(𝑘1)𝐻(𝑘1)3𝐹(𝑘1)𝐹(𝑘2)3𝐻(𝑘1)𝐻(𝑘2)

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

𝐹(𝑘1)𝐻(𝑘1)𝐹(𝑘)𝐻(𝑘)

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

𝐹(𝑘)𝐻(𝑘)𝐹(𝑘1)𝐻(𝑘1)

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

那么立即推出:

𝐹(𝑘1)𝐻(𝑘1)𝐹(𝑘2)𝐻(𝑘2)=𝐹(2)𝐻(2)𝐹(1)𝐻(1)

FH 定理得证。

利用 FH 定理,把𝐹(1)=1,𝐹(2)=3,𝐻(1)=3,𝐻(2)=8带入:

𝐹(𝑛1)𝐻(𝑛1)𝐹(𝑛2)𝐻(𝑛2)=1

于是就爽了嘛!

𝐴=𝐻(𝑛)+𝐹(𝑛1)+(1)1=𝐻(𝑛)+𝐹(𝑛1)2

进一步我们发现…… 设𝑅(𝑛)=𝐻(𝑛)+𝐹(𝑛1)2

那么立即有:

𝑅(𝑛)=3𝐻(𝑛1)𝐻(𝑛2)+3𝐹(𝑛2)𝐹(𝑛3)2=3(𝑅(𝑛1)+2)(𝑅(𝑛2)+2)2=3𝑅(𝑛1)𝑅(𝑛2)+2

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

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

法二:DP

转载自 boshi

如果用 f[x] 表示加入了𝑥个周围的点后的方案数,我们首先想到的递推式是:𝑓[𝑖]=𝑖𝑗=1𝑓[𝑖𝑗]𝑗

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

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

我们再设:𝑔[𝑖]=𝑖𝑗=2𝑓[𝑖𝑗]𝑗(𝑗1)

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

这样的𝑓[𝑛]+𝑔[𝑛]就是我们要求的轮状病毒的数量。

下面我们思考如何快速求出𝑓𝑔

多阶差分

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

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

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

Δ𝑓[𝑖]=𝑖𝑗=1𝑓[𝑖𝑗]𝑗𝑖1𝑗=1𝑓[𝑖1𝑗]𝑗=𝑖𝑗=0𝑓[𝑖𝑗]𝑗𝑖1𝑗=0𝑓[𝑖1𝑗]𝑗(𝑓[𝑖]0=𝑓[𝑖1]0=0)=𝑖𝑗=0𝑓[𝑗](𝑖𝑗)𝑖1𝑗=0𝑓[𝑗](𝑖1𝑗)(交换枚举顺序)=𝑖𝑗=0𝑓[𝑖]𝑔[𝑖]=𝑖𝑗=2𝑓[𝑖𝑗]𝑗(𝑗1)=𝑖𝑗=0𝑓[𝑖𝑗]𝑗(𝑗1)(𝑓[𝑖1]10=0)Δ𝑔[𝑖]=𝑖𝑗=0𝑓[𝑖𝑗]𝑗(𝑗1)𝑖1𝑗=0𝑓[𝑖𝑗1](𝑗1)(𝑗2)=𝑖𝑗=0𝑓[𝑗](𝑖𝑗)(𝑖𝑗+1)𝑖1𝑗=0𝑓[𝑗](𝑖𝑗)(𝑖𝑗1)=𝑖𝑗=0𝑓[𝑗](𝑖𝑗)2(𝑖𝑖=0)Δ2𝑔[𝑖]=𝑖𝑗=0𝑓[𝑗](𝑖𝑗)2𝑖1𝑗=0𝑓[𝑗](𝑖𝑗1)2=𝑖𝑗=02𝑓[𝑗](𝑖𝑖=0)Δ3=2𝑓[𝑖]

我们维护𝑓[𝑖]的前缀和,以及𝑓[𝑖𝑗]𝑗的前缀和,每次将𝑓[𝑖]累加进𝑓[𝑖]的前缀和,将𝑓[𝑖]的前缀和累加进𝑓[𝑖𝑗]𝑗的前缀和,𝑔[𝑖]同理。

行列式解法:

#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;
}

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