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

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

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

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

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

Fn=ai=nai

递推一下, Fn=nk=1kFnk,F0=1

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

answer(n)=Fn+k=2n(k1)kFnk

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

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

我的解法能不能优化呢?

先展开观察一番:

Fn=1Fn1+2Fn2++(n1)F1+nF0Fn+1=1Fn+2Fn1+3Fn2++nF1+(n+1)F0

再作个差,移项:

Fn+1=Fn+k=0nFk(n1),F0=F1=1

递推一下前缀和,就把时间复杂度成功降至 O(n) ,从28ms降为0ms。空间也可以优化到 O(1)

发现 Fn 是间隔一项的斐波那契数列!

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

你可能感兴趣的:(数学-递推,高精度)