您的位置:首页 > 其它

[bzoj 1002] [FJOI2007]轮状病毒:数学,递推,高精度

2016-12-04 16:12 387 查看
题意:n轮状病毒是这样一种病毒:n个基原子围成一圈,中间是一个核原子,基原子和核原子、基原子和相邻两个核原子之间可以有通道,任意两原子之间有且仅有一条通道,求有多少n轮状病毒(n<=100)。这不是在数同分异构体,经旋转、翻转后相同的轮状病毒视作不同的。

原本觉得是矩阵树定理,数据范围也挺像O(n3),就没深入地思考这道题。Finger_Leader同学说他是本校第一位写出这道题的同学,并且告诉我用不着高斯消元,于是我想了想。

看起来,我们应该建递推。但是连通性的约束不好处理。

换一个角度。观察图形。所有轮状病毒都长这样:外面分成几瓣,每一瓣中有一条通道连接核原子。断掉外围一条边,问题转化为求Fn=∑∑ai=n∏ai

递推一下,Fn=∑nk=1kFn−k,F0=1。

先前断掉了一条边,让我们把少算的情况加回来。如果这条边所属的那一瓣中共有k个结点,那么答案要加上(k−1)个kFn−k,于是

answer(n)=Fn+∑k=2n(k−1)kFn−k

直接这样写,时间复杂度是O(n2)。别忘了高精度。我开了long long,发现n=100时答案是正的,就提交了,结果WA。又试了几个数,立刻变负……看来我对数量级还是缺乏概念。

但是Finger_Leader同学不是这样写的,网上的题解也不是这样写的……大多用的是矩阵树定理,由于本题轮状病毒长得很有规律,所以可以推导出一个简单的公式,无须高斯消元。忽略高精度,时间复杂度为O(n)。

我的解法能不能优化呢?

先展开观察一番:

Fn=1Fn−1+2Fn−2+⋯+(n−1)F1+nF0Fn+1=1Fn+2Fn−1+3Fn−2+⋯+nF1+(n+1)F0

再作个差,移项:

Fn+1=Fn+∑k=0nFk(n≥1),F0=F1=1

递推一下前缀和,就把时间复杂度成功降至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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  数学 递推 高精度