题意:n轮状病毒是这样一种病毒:n个基原子围成一圈,中间是一个核原子,基原子和核原子、基原子和相邻两个核原子之间可以有通道,任意两原子之间有且仅有一条通道,求有多少n轮状病毒(n<=100)。这不是在数同分异构体,经旋转、翻转后相同的轮状病毒视作不同的。
原本觉得是矩阵树定理,数据范围也挺像O(n3),就没深入地思考这道题。Finger_Leader同学说他是本校第一位写出这道题的同学,并且告诉我用不着高斯消元,于是我想了想。
看起来,我们应该建递推。但是连通性的约束不好处理。
换一个角度。观察图形。所有轮状病毒都长这样:外面分成几瓣,每一瓣中有一条通道连接核原子。断掉外围一条边,问题转化为求
递推一下,Fn=∑nk=1kFn−k,F0=1。
先前断掉了一条边,让我们把少算的情况加回来。如果这条边所属的那一瓣中共有k个结点,那么答案要加上(k−1)个kFn−k,于是
直接这样写,时间复杂度是O(n2)。别忘了高精度。我开了long long,发现n=100时答案是正的,就提交了,结果WA。又试了几个数,立刻变负……看来我对数量级还是缺乏概念。
但是Finger_Leader同学不是这样写的,网上的题解也不是这样写的……大多用的是矩阵树定理,由于本题轮状病毒长得很有规律,所以可以推导出一个简单的公式,无须高斯消元。忽略高精度,时间复杂度为O(n)。
我的解法能不能优化呢?
先展开观察一番:
再作个差,移项:
递推一下前缀和,就把时间复杂度成功降至O(n),从28ms降为0ms。空间也可以优化到O(1)。
发现Fn是间隔一项的斐波那契数列!
#include <cstdio>
using namespace std;
typedef long long ll;
const int MAX_N = 100;
struct Big {
const static int w = 5, base = 1e9, lg = 9;
int x[w];
Big(ll a=0)
{
*this = a;
}
Big operator=(ll a)
{
for (int i = 0; i < w; ++i, a /= base)
x[i] = a % base;
return *this;
}
Big operator+(const Big& b) const
{
Big c;
for (int i = 0, f = 0; i < w; ++i)
if (f = (c.x[i] = x[i] + b.x[i] + f) >= base)
c.x[i] -= base;
return c;
}
Big operator+=(const Big& b)
{
return *this = *this + b;
}
Big operator*(const Big& b) const
{
Big c;
for (int i = 0; i < w; ++i)
for (int j = 0, f = 0; i+j < w; ++j) {
ll t = (ll)b.x[i]*x[j] + c.x[i+j] + f;
c.x[i+j] = t % base;
f = t / base;
}
return c;
}
void print() const
{
int i = w-1;
while (i && !x[i])
--i;
printf("%d", x[i--]);
while (i >= 0)
printf("%0*d", lg, x[i--]);
}
};
int main()
{
int n;
scanf("%d", &n);
Big f(1), S(1), ans(n*(n-1));
for (int i = 1; i <= n; ++i) { // f[i]
if (i > 1)
f += S;
S += f;
if (i <= n-2)
ans += Big((n-i)*(n-i-1))*f;
}
(ans+f).print();
return 0;
}