【转载】1002: [FJOI2007]轮状病毒

Description

2011年10月06日 - vfleaking - vfleaking的博客
  给定n(N<=100),编程计算有多少个不同的n轮状病毒。

Input

第一行有1个正整数n。

Output

将编程计算出的不同的n轮状病毒数输出

Sample Input

3

Sample Output

16

用基尔霍夫矩阵(教程:http://www.4ucode.com/Study/Topic/1940063),使用高斯消元解行列式,时间复杂度O(n^3)似乎可以AC。
其实如果你仔细观察矩阵,可以发现它是这样的:(消去了0号点,就是病毒中间的那个圆坨坨)
 3  -1  0  0  0  ...  0  0  0  -1
 -1  3  -1  0  0  ...  0  0  0  0
 0  -1  3  -1  0  ...  0  0  0  0
 0  0  -1  3  -1  ...  0  0  0  0
 0  0  0  -1  3  ...  0  0  0  0
 ...  ...  ...  ...  ...  ...  ...  ...  ...  ...
 0  0  0  0  0  ...  3  -1  0  0
 0  0  0  0  0  ...  -1  3  -1  0
 0  0  0  0  0  ...  0  -1  3  -1
 -1  0  0  0  0  ...  0  -1  3
下面就简单了。设F(n)为n轮状病毒的个数,然后自己手算一下上面的矩阵,
可以发现,F(n) = 3F(n - 1) - F(n - 2) + 2。
由此可以得到O(n)的算法。
我无聊,用矩阵乘法+快速幂进行优化……O(logn)

=============================================
update:
吐槽:你说是O(logn)就是O(logn)?敢不敢把高精度计算的时间也算上啊!慢得要死啊!自己弱就不要写题解啊!我当年是sb吗!!!
同学们,只要写高精度加减暴力递推就行了~

我不知道我上面这种含糊不清的题解导致了多少人晕晕地混过去了,深表歉意。时隔一年半,我把详细证明补上

/* 
这一段神犇无视啊……
来看这篇题解的同学八成是跟我当年一样是才开始做衡阳八中的新手。
这道题新手可以拿来练练高精度。
估计也没人喜欢看证明的吧。但是对于新手还是建议去看看基尔霍夫矩阵。
*/

有公式恐惧症的同学,治病的时候到了!!

首先行列式有很多性质,第a行 * k 加到 第b行上去,行列式的值不变。
三角行列式的值等于对角线元素之积。
第a行与第b行互换,行列式的值取反。
常数 * 行列式,可以把常数乘到某一行里去。
如果你行列式不是很熟~建议先google行列式~不然下面会看晕~

那么我们现在对行列式进行变换,我们把第一行与第二行交换,再把第二行与第三行交换...,再把第n - 1行与第n行变换。这些都是行列式初等行列变换,得到新的行列式:
 -1  3  -1  0  0  ...  0  0  0  0
 0  -1  3  -1  0  ...  0  0  0  0
 0  0  -1  3  -1  ...  0  0  0  0
 0  0  0  -1  3  ...  0  0  0  0
 ...  ...  ...  ...  ...  ...  ...  ...  ...  ...
 0  0  0  0  0  ...  3  -1  0  0
 0  0  0  0  0  ...  -1  3  -1  0
 0  0  0  0  0  ...  0  -1  3  -1
 -1  0  0  0  0  ...  0  -1  3
 3  -1  0  0  0  ...  0  0  0  -1
这个行列式跟一开始的那个行列式的值不一定相等。
因为我们是通过n - 1次交换行的操作得到的,
为了说话方便我们称一开是的那个行列式为A,上面我刚写的是B
那么由行列式性质得:A = (-1)^(n - 1) * B
-1好烦人对吧……
好,那么现在可以正大光明地处理B了~
利用行列式性质,来手算这个行列式。之所以刚才有那么一步,就是为了方便手算。
因为观察B,发现就只剩下左下角的-1、3、-1三个倒霉了。

首先看倒数第二行:
 -1  0  0  0  0  ...  0 0   -1  3
用第一行的:
 -1   3   -1   0   0   ...   0   0   0   0
乘以-1来消,得:
 0 -3  1  0  0  ...  0 0   -1  3
然后再用第二行的:
 0 -1   3   -1   0   ...   0   0   0   0
乘以-3来消,得:
 0 0  -8  3  0  ...  0 0   -1  3
这样就有了初步感觉了~
现在把这个过程一般化,假设现在正在处理的这一行的第k个和第k+1个时是这样的:
0 0 ... F(k) G(k) 0 0 ... -1 3
总能找到上面的某一行:
0 0 ... -1 3 -1 0 ... 0 0
乘以F(k)来消,根据我们设的数,应该得到的是:
0 0 ... 0 F(k + 1) G(k + 1) 0 ... -1 3
于是得到:
F(k + 1) = G(k) + 3 * F(k)
G(k + 1) = -F(k)
整合一下:
F(k + 1) = 3 * F(k) - F(k - 1)
从初始的行和消了一次之后的行中取得边界条件:F(1) = -1,F(2) = -3

经过了漫长的消来消去,我们最终可以把倒数第二行变为:
 0 0  0   0   0   ...  F(n - 3) G (n - 3)     -1   3
然后用倒数第四行:
 0  0  0  0  0  ...  -1  3  -1  0
乘以F(n - 3)来消:
 0 0  0   0   0   ...  0 F (n - 2)    G(n - 2)  - 1   3
还剩下一步……用倒数第三行:
 0  0  0  0  0  ...  0  -1  3  -1
乘以F(n - 2)来消:
 0 0  0   0   0   ...  0   0  F(n - 1)  - 1  G(n - 1) +  3
好现在搞定了倒数第二行,来看看成果:(设f = F(n - 1) - 1, g = G(n - 1) + 3)
 -1  3  -1  0  0  ...  0  0  0  0
 0  -1  3  -1  0  ...  0  0  0  0
 0  0  -1  3  -1  ...  0  0  0  0
 0  0  0  -1  3  ...  0  0  0  0
 ...  ...  ...  ...  ...  ...  ...  ...  ...  ...
 0  0  0  0  0  ...  3  -1  0  0
 0  0  0  0  0  ...  -1  3  -1  0
 0  0  0  0  0  ...  0  -1  3  -1
 0  0  0  0  0  ...  0  f  g
 3  -1  0  0  0  ...  0  0  0  -1


好,现在来搞倒数第一行。
和倒数第二行的方法是类似的。
再设函数H(k)和I(k),意义与F(k)、G(k)类似,得:
H(k + 1) = I(k) + 3 * H(k)
I(k + 1) = -H(k)
其实跟F、G的递推式是一样的我会乱说?
H(k + 1) = 3 * H(k) - H(k - 1)
边界条件是: H(1) = 3,H(2) = 8
最后使劲搞一搞,倒数第一行就成了:
 0  0   0   0   0   ...   0   0  H(n - 1)  I(n - 1)  - 1
再来看成果: (设h = H(n - 1), i = I(n - 1) - 1)
 -1  3  -1  0  0  ...  0  0  0  0
 0  -1  3  -1  0  ...  0  0  0  0
 0  0  -1  3  -1  ...  0  0  0  0
 0  0  0  -1  3  ...  0  0  0  0
 ...  ...  ...  ...  ...  ...  ...  ...  ...  ...
 0  0  0  0  0  ...  3  -1  0  0
 0  0  0  0  0  ...  -1  3  -1  0
 0  0  0  0  0  ...  0  -1  3  -1
 0  0  0  0  0  ...  0  f  g
 0  0  0  0  0  ...  0  0  h  i

f是不会为0的吧……
那么用倒数第二行来消倒数第一行,得:
 0  0   0   0   0   ...   0   0  0  i - g * (h / f)
现在这个行列式已经是三角行列式了。
那么它的值就是对角线元素之积。
于是乎:B = (-1) * (-1) * (-1) * ... * (-1) * f * (i - g * (h / f))
一共有n - 2个-1
那么如前文所述:A = (-1)^(n - 1) * B
又因为: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) + 3 * H(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)| - 1
|F(n - 2) H(n - 2)|
那啥那个长得丑丑的占了两行的表示的是二阶行列式。
发现不能化简了?
没关系!
在行列式上动动手脚吧!

【FH定理】
对于任意大于2的k有:
|F(k - 1) H(k - 1)| = |F(2) H(2)|
|F(k - 2) H(k - 2)| |F(1) H(1)|
【FH定理的证明】
对于行列式:
|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) |
|F(k - 1) * 3 - F(k - 2) H(k - 1) * 3 - 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(2) H(2)|
|F(k - 2) H(k - 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)| = -1
|F(n - 2) H(n - 2)|


于是就爽了嘛!
A = H(n) + F(n - 1) + (-1) - 1
化简:
A = H(n) + F(n - 1) - 2

进一步我们发现……
设R(n) = H(n) + F(n - 1) - 2
那么立即有:
R(n) = 3 * H(n - 1) - H(n - 2) + 3 * F(n - 2) - F(n - 3) - 2
= 3 * (R(n - 1) + 2) - (R(n - 2) + 2) - 2
R(n) = 3 * R(n - 1) - R(n - 2) + 2

所以,得到如下结论:
【轮状病毒定理】
轮状病毒的方案数满足递推式F(n) = 3 * F(n - 1) - F(n - 2) + 2, 其中F(1) = 1, F(2) = 5

目前这题的这个我只会这么证。
肯定是有更简单方法证……这样证肯定不是官方解法……
其实这种题还是打表找规律吧汗。
但是这种题要是出现在了考试中,我会写状压DP。记录0、1、n - 1三个点间的连边状态,暴力DP一下。应该是可捉的。这才是正常思路吧囧。BZOJ的discuss里面有人发了类似的非数学方法。

=============================================

代码如下:(小心观看)
(matrix类和高精度的类我就不写了,摆不下)

int main()
{
    freopen("1002.in", "r", stdin);
    freopen("1002.out", "w", stdout);
    
    int n;
    
    cin >> n;
    if (n == 1)
        cout << 1 << endl;
    else if (n == 2)
        cout << 5 << endl;
    else
    {
        matrix t(3, -1, 2,
                 1, 0 , 0,
                 0, 0 , 1
                 );
        
        matrix res = cf(t, n - 2);
        
        cout << (res.m[0][0] * 5 + res.m[0][1] + res.m[0][2]) << endl;
    }
    
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值