BZOJ 1002: [FJOI2007]轮状病毒
Description
轮状病毒有很多变种,所有轮状病毒的变种都是从一个轮状基产生的。一个N轮状基由圆环上N个不同的基原子
和圆心处一个核原子构成的,2个原子之间的边表示这2个原子之间的信息通道。如下图所示
N轮状病毒的产生规律是在一个N轮状基中删去若干条边,使得各原子之间有唯一的信息通道,例如共有16个不
同的3轮状病毒,如下图所示
现给定n(N<=100),编程计算有多少个不同的n轮状病毒
Input
第一行有1个正整数n
Output
计算出的不同的n轮状病毒数输出
Sample Input
3
Sample Output
16
HINT
Source
Solution
先考虑成中心点到一条链上的方案数,f(x)表示链的成为x
显然把一条链拆成若干个区间,每个区间与中心点只连一条边
则(f(i)=sum_{j=1}^ijf(i-j)),(f(0)=1)
在考虑环的情况,(g(i))为一个长为(i)的环的方案数,先考虑其中
一个点,枚举此点所在的区间长,那么这个区间有(j)种取法,那
这个区间就把还断成长度为(i-j)的链了
则(g(i)=sum_{j=1}^ij^2f(i-j))
上方法复杂度为(O(n^2)),现在考虑优化。
使用做差的方法
(F_{n-1}=1F_{n-2}+2F_{n-3}+...+(n-1)F_0)
(F_n=1F_{n-1}+2F_{n-2}+...+nF_{0})
推得
(F_n=F_{n-1}+sum_{k=0}^{n-1}F_k(ngeq1),F_0=F_1=1)
计算一下前缀和,复杂度就成了(O(n))
注意:需要高精度
Code
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define rep(i,x) for(int i=head[x];i;i=next[i])
#define mem(a,x) memset(a,x,sizeof(a))
typedef long long LL;
typedef double DB;
using namespace std;
inline int read() {
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9') f=(ch=='-')?-1:f,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+(ch-'0'),ch=getchar();return f*x;
}
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;LL t;
fo(i,0,w-1) for (int j=0,f=0;i+j<w;j++) {
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=read();
Big f(1),S(1),ans(n*(n-1));
fo(i,1,n) {
if(i>1) f=f+S;
S+=f;
if(i<=n-2) ans=ans+Big((n-i)*(n-i-1))*f;
}
(ans+f).print();
return 0;
}