【供参考】
【分析】
很明显的生成树计数,用基尔霍夫矩阵求行列式可以解出来。但是如果使用递归定义求解,会TLE;如果使用高斯消元,会出现数太大导致无法运算。所以这是不行的。
然而我们还没有利用这个行列式的特殊性,还有计数问题通常从组合数学或者动态规划的角度入手,而且输入数据这么少,所以应该使用递推的方法。
我们设
f[i]
表示
i
阶轮状病毒的个数。
接下来,我们大胆地猜想它是可以线性递推的。
先把几个小的解给求出来。
这里可以手算,也可以写一个暴力的程序,但是最好还是写一个暴力的程序,不然会很烦的,而且耗时间。
接下来,我们就要求解线性递推式,和上面一样,要么手算,要么打个解方程的代码。
由于我们求了
f(1),f(2),f(3),f(4),f(5),f(6)
,所以我们最多可以设
4
个元,我们这里就只设
设
f(n)=af(n−1)+bf(n−2)+c
①当
n=4
时,
16a+5b+c=45
②当
n=5
时,
45a+16b+c=121
③当
n=6
时,
121a+45b+c=320
由①②③得
即解矩阵
解得 a=3,b=−1,c=2
即 f(n)=3f(n−1)−f(n−2)+2 。
问题解决,最后注意要高精度即可。
目前还欠了个账,就是直接用行列式求递推式的推导。数学基础暂时不够,以后再补。
【代码】
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
const int N=101;
struct Q
{
int p[N];
}f[N],t;
int n;
inline Q plus(Q qa,Q qb)
{
Q s; memset(s.p,0,sizeof s.p); s.p[0]=(qa.p[0]<qb.p[0]?qb.p[0]:qa.p[0]);
int m,g=0;
for (int i=1;i<=s.p[0];i++)
{
m=qa.p[i]+qb.p[i]+g;
s.p[i]=m%10;
g=m/10;
}
if (g) s.p[++s.p[0]]=1;
return s;
}
inline Q minus(Q qa,Q qb)
{
Q s; memset(s.p,0,sizeof s.p); s.p[0]=qa.p[0];
int m,g=0;
for (int i=1;i<=s.p[0];i++)
{
m=qa.p[i]-qb.p[i]-g;
if (m<0) m+=10,g=1; else g=0;
s.p[i]=m;
}
for (;!s.p[s.p[0]];s.p[0]--);
return s;
}
int main(void)
{
scanf("%d",&n);
f[1].p[0]=f[1].p[1]=1;
f[2].p[0]=1,f[2].p[1]=5;
t.p[0]=1,t.p[1]=2;
for (int i=3;i<=n;i++)
f[i]=plus(minus(plus(f[i-1],plus(f[i-1],f[i-1])),f[i-2]),t);
for (int i=f[n].p[0];i;i--) printf("%d",f[n].p[i]);
printf("\n");
return 0;
}
【小结】
这道题,给了我们一些很好的思路。
首先要明确一点:
当设计出一个算法,且想不到更好的注意时,要想想自己没有利用哪些题目的特殊性,从特殊性进行方法的调整。
然后,这道题是计数问题,下面总结的即一些计数问题的技巧方法。
计数问题,入手的角度通常有两个:组合数学,动态规划。当然还有一些特殊的模型,如本题生成树计数用基尔霍夫矩阵求行列式值。
对于从动态规划入手的问题,有一个常见的技巧。
①首先算出小数据的答案,然后求解递推式。
②能这样干的特征通常是:输入数据少。
③递推式通常我们猜想是线性的,有时可能也会涉及到 f(n)2 这种恶心的东西。
④设元时不要遗漏常数项。
⑤设元个数
p
当前我们求了
首先能设
p
个元,则有:
那么 f[1],f[2],...,f[p−1] 都不可以用于方程, f[p],f[p+1],...,f[n] 都可以,即可以列 n−p+1 个方程。
要能求解,所以 p≤n−p+1 即 p≤n+12 。
⑥求值的会特别的讨厌,在这时候有两种方法:
- 方法1:打个暴力的代码
- 方法2:手算
求解递推式的过程同样讨厌,一样的有以上两种方法。
然而像本题这样的都涉及到高斯消元的,打个程序的性价比就会比较高了。
反正——酌情而定吧。