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

本文探讨了一种计算n轮状病毒结构数量的方法,通过建立递推关系并结合前缀和技巧,将时间复杂度从O(n^2)优化至O(n)。此外,还分享了一个高效的大整数运算实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意: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 <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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值