【BZOJ】1002:輪狀病毒(基爾霍夫矩陣【附公式推導】或打表)


Description

  輪狀病毒有很多變種,所有輪狀病毒的變種都是從一個輪狀基產生的。一個N輪狀基由圓環上N個不同的基原子
和圓心處一個核原子構成的,2個原子之間的邊表示這2個原子之間的信息通道。如下圖所示:

TIM截圖20171026154559

  N輪狀病毒的產生規律是在一個N輪狀基中刪去若干條邊,使得各原子之間有唯一的信息通道,例如共有16個不
同的3輪狀病毒,如下圖所示:

TIM截圖20171026154621

  現給定n(N<=100),編程計算有多少個不同的n輪狀病毒。

Input

  第一行有1個正整數n。

Output

  計算出的不同的n輪狀病毒數輸出。

Sample Input

3

Sample Output

16

這里是題目鏈接:[BZOJ]1002:輪狀病毒

這里是題解:

方法一:打表找規律

先暴力求出一部分答案:

這里是暴力打表代碼:

 

#include<cstdio> #include<cstring> #include<iostream> #include<algorithm>
#define M 110
using namespace std; struct abcd{ int to,next; bool ban; }table[M<<2]; int head[M],tot=1; int n,ans; void Add(int x,int y) { table[++tot].to=y; table[tot].next=head[x]; head[x]=tot; } int fa[M],v[M],q[M],r,h; bool BFS() { int i; r=h=0; memset(v,0,sizeof v); memset(fa,-1,sizeof fa); q[++r]=0; while(r!=h) { int x=q[++h]; for(i=head[x];i;i=table[i].next) if(!table[i].ban) { if(table[i].to==fa[x]) continue; if(v[table[i].to]) return 0; fa[table[i].to]=x; v[table[i].to]=1; q[++r]=table[i].to; } } if(r<=n) return 0; return 1; } void DFS(int x) { if(x+x>tot) { if( BFS() ) ++ans; return ; } table[x<<1].ban=table[x<<1|1].ban=0; DFS(x+1); table[x<<1].ban=table[x<<1|1].ban=1; DFS(x+1); } int main() { int i; for(int j=1;j<=12;j++){ memset(head,0,sizeof head); tot=1;ans=0; n=j; for(i=1;i<=n;i++) Add(0,i),Add(i,0),Add(i,i%n+1),Add(i%n+1,i); DFS(1); cout<<ans<<' '; }printf("\n"); return 0; }
暴力打表

 

打出1-15的表(like this):

1     5      16     45      121

320   841     2205    5776     15125

39601  103680   271441   710645   1860496

Process exited after 48.06 seconds with return value 0

想將所有表打出來估計是不可能的事情,所以需要找規律。

這里是規律:

1 5 16 45 121 320 841 2205 5776 15125 39601 103680 271441 710645 1860496【1-15的答案】

第1、3、5、7...[奇數位]位是平方數 :

  1*1  4*4  11*11   29*29   76*76   199*199  521*521...

第2、4、6、8...[偶數位]位除以5后也是平方數:

  5*1*1  5*3*3  5*8*8  5*21*21  5*55*55   5*144*144 ...

【最美妙的事情發生了】:

奇數位:1  3  4  7  11  18  29  47  76...[粗體為原奇數位的算術平方根]

偶數位:1  2  3  5  8   13  21  34  55...[粗體為原偶數位除以5后的算術平方根]

(這個就屬於改版的斐波拉契數列,只是初始值不一樣)

然后求【改版斐波拉契數列】的值就行了。(但是要注意高精度!)

這里是推薦內容:

其實一般情況下還是很難看出來這個是改版斐波拉契數列的間隔值。

所以這里【傾情】推薦一個網站:Wolframalpha

這里輸入之前打表的值:

然后這里就可以看見更多的值:

兩三次【More】之后,基本上就有100個數了,然后就可以直接暴力打表。

【但是考場上不能用so sad :( 】

附:其實網上還流傳了一種用這些打表出來的數:1 5 16 45 121 320 841 2205 5776 ……

得出了一個遞推式:a[i]=a[i-1]*3-a[i-2]+2 ,用這個式子同樣能夠得出答案。

當然這個只是在[亂搞],找規律一般只使用於時間不夠或者真的推不出來遞推式的情況!

這里是找規律的代碼:

 

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
struct Num
{
    int a[1111],len;
    void Print()
    {
        for (int i=len-1;i>=0;i--)
            printf("%d",a[i]);
        printf("\n");
    }
}a[111],b[111];
int n;
Num operator ^ (Num n,int b)
{
    for (int i=0;i<n.len;i++)
        n.a[i]*=b;
    for (int i=0;i<n.len;i++)
    {
        n.a[i+1]+=n.a[i]/10;
        n.a[i]%=10;
    }
    while (n.a[n.len]!=0)
    {
        n.a[n.len+1]+=n.a[n.len]/10;
        n.a[n.len]%=10;
        n.len++;
    }
    return n;
}
Num operator * (Num a,Num b)
{
    Num c;
    c.len=a.len+b.len;
    memset(c.a,0,sizeof c.a);
    for (int i=0;i<a.len;i++)
        for (int j=0;j<b.len;j++)
            c.a[i+j]+=a.a[i]*b.a[j];
    for (int i=0;i<c.len;i++)
    {
        c.a[i+1]+=c.a[i]/10;
        c.a[i]%=10;
    }
    while (c.a[c.len-1]==0) c.len--;
    return c; 
}
Num operator - (Num a,Num b)
{
    for (int i=0;i<b.len;i++)
        a.a[i]-=b.a[i];
    for (int i=0;i<a.len;i++)
        if (a.a[i]<0)
        {
            a.a[i]+=10;
            a.a[i+1]--;
        }
    while (a.a[a.len-1]==0) a.len--;
    return a;
}
void  eq(Num &a,Num b)
{
    a.len=b.len;
    for (int i=0;i<a.len;i++) a.a[i]=b.a[i];
}
int main()
{
    scanf("%d",&n);
    a[1].a[0]=1,a[2].a[0]=4;
    b[1].a[0]=1,b[2].a[0]=3;
    a[1].len=a[2].len=b[1].len=b[2].len=1;
    for (int i=3;i<=n;i++)
    {
        eq(a[i],(a[i-1]^3)-a[i-2]);
        eq(b[i],(b[i-1]^3)-b[i-2]);
    }
    Num ans;
    if (n%2==1) eq(ans,a[(n+1)/2]*a[(n+1)/2]);
    else eq(ans,b[n/2]*b[n/2]^5);
    ans.Print();
    return 0;
}
【BZOJ】1002:輪狀病毒

 

方法二:【基爾霍夫矩陣】

這里是預備知識:

這個題是求兩兩之間只有一條直接或間接路徑(沒有環,形成一棵樹)的方案數。

一個專有名詞叫做:【生成樹計數】

生成樹計數:通常情況是由Kirchhoff's Matrix-Tree Theorem(基爾霍夫矩陣矩陣樹定理)求解。

基爾霍夫矩陣:也叫導納矩陣、拉普拉斯矩陣或離散拉普拉斯算子。

給定一個有n個頂點的圖G,它的拉普拉斯矩陣 定義為:  L=D-A 

其中:D矩陣叫做【度矩陣】;A矩陣叫做【鄰接矩陣】

什么是度矩陣?

對於這種【無向圖】,每一個點的度就是它連邊的個數

【for example】:4的度數就是3(和3,5,6連邊)

用這些度構成度矩陣

僅當矩陣中【 i==j 】時D[i][j]才有值:此時D[i][j] = i 號點的度數

如果【 i!=j 】,D[i][j]就賦值為0.

所以上面這個圖的度矩陣為:

什么是鄰接矩陣?

當 i 號點和 j 號點有連邊的時候,將A[i][j]=1,A[j][i]=1;(雙向邊)

其余 i、j 沒有連邊的 A[i][j]=0;

【當 i==j 時,A[i][j]=0 】

例子的鄰接矩陣為:

所以基爾霍夫矩陣【 L=D-A 】為:

現在已經求得基爾霍夫矩陣,那么

Kirchhoff's Matrix-Tree Theorem(基爾霍夫矩陣矩陣樹定理)又是什么呢?

基爾霍夫矩陣C的任意余子式Mij,Mij的行列式的值就是圖G的生成樹個數。

這里是大神博客:生成樹計數問題——矩陣樹定理及其證明

(證明見這位大神博客,蒟蒻表示不會證明這個,只會用ORZ

什么是余子式?

簡單來說就是:一個行列式的余子式Mij就是去掉aij所在的行和列。

M23的余子式就是:TIM截圖20171024210534

(這里第三行刪掉了,第三列也刪掉了)

這里是本題真正的題解:

首先,根據以上條件,對於該圖構造基爾霍夫矩陣

無標題

(刪除中心原點的行和列,設n為圈上的點的個數)

TIM截圖20171024210534

對角線aij表示與該點相連的個數。

如果i點和j點有連邊,那么aij就賦值為-1.

(對於第一排和最后一排,因為這個圖是一個圓圈,所以n與1相連)

然后計算出這個行列式的值就是本題的答案了,將該答案設為g[n]

這里是計算過程:

方法①:高斯消元 O(n^3) 大概可以過吧,沒寫過ORZ。

方法②:利用行列式的性質推導。

這里有一張大圖,是推導全過程,結合此圖瀏覽下列題解能更方便理解

建議保存本圖,然后用畫圖工具打開,邊看圖,邊理解博客。

預備知識:(建議先了解行列式的各種性質)

對於本題,這里主要用兩個性質

1.n階行列式,按行列展開。

展開方法:行列式等於它的任意一行(列)的各元素與其對應的代數余子式乘積之和。

即:TIM截圖20171024210534

代數余子式的計算方法:對於一個4階行列式的余子式M23.

無標題

代數余子式A23=M23*(-1)^(2+3)=-M23

所以公式為:Aij=Mij*(-1)^(i+j)

2.三角行列式的計算:主對角線以上的部分全為0。

無標題

注:建議先熟悉行列式的各種性質

現在,將本題的行列式按最后一行展開。

最后一行第一項展開為:(為了之后方便,將其設為①號式子[n-1階行列式])

TIM截圖20171025213310

最后一行倒數第二項展開為:(為了之后方便,將其設為②號式子[n-1階行列式])

TIM截圖20171025213350

最后一行最后一項展開為:(為了之后方便,將其設為③號式子[n-1階行列式])

TIM截圖20171025213434

因為③號式子形式特別,現將對角線為3,然后兩邊為-1的式子設為f[i]

所以③號式子設為f[n-1].(注意這里和之前的基爾霍夫矩陣不一樣,因為這里a1n、an1不再為-1)

原式=①號+②號+③號

對於①號式子:按第一行展開。

①號式子第一項展開得到下列式子[n-2階式子]:

TIM截圖20171026135739

明顯運用之前提到的性質2,可以求出,該式子的值為:(-1)^(n-1)

①號式子最后一項得到下列式子[n-2階式子]:

TIM截圖20171026135956

明顯這個矩陣就像f函數的矩陣形式

所以①號式子的值可以表示為:-1-f[n-2]

對於②號式子:按最后一列展開。(注意是列)

②號式子按最后一列第一項展開得到下列式子[n-2階式子]:

TIM截圖20171026140308

根據三角行列式計算方法,得到該行列式的值為:-1

②號式子按照最后一項展開得到下列式子[n-2階式子]:

TIM截圖20171026141411

明顯形式又是相同的,所以該行列式的值可以表示為:-f[n-2]

所以②號式子的值為:-f[n-2]-1

而對於③號式子:

TIM截圖20171025213826

本身形式相同,所以表示為:3*f[n-1]

綜上g[n]=①+②+③=-1-f[n-2]-f[n-2]-1+3*f[n-1]=3*f[n-1]-2*f[n-2]-2

現在就要求出f函數之間的關系

對於無系數的③號式子:按照最后一行展開。

倒數第二項展開為:(為了之后方便,將其設為④號式子[n-2]階行列式])

無標題

最后一項展開為:([n-2]階行列式)

TIM截圖20171025214445

明顯這個形式就是之前的f函數,所以可以表示為:3*f[n-2].

對於④號式子:按照最后一行展開。

倒數第二項展開:

無標題

因為這個行列式中有一列為0,按照行列式的定義,它的值為0.

最后一項展開:([n-3]階行列式)

TIM截圖20171026142632

這個行列式的值就可以表示為:-f[n-3].

所以④號式子的值為:-f[n-3].

根據③號式子、④號式子:可以得出f[n-1]=3*f[n-2]-f[n-3]

現在有兩個式子:

g[n]=3*f[n-1]-2*f[n-2]-2

f[n]=3*f[n-1]-f[n-2]

如何將它化成一個只有g函數的遞推式:

首先g函數f函數的關系式為g[i]=3*f[i-1]-2*f[i-2]-2

因為3*f[i-1]=9*f[i-2]-3*f[i-3]

  3*g[i-1]=9*f[i-2]-6*f[i-3]-6

所以(用g[i-1]來替代f[i-1])

g[i]=3*g[i-1]+3*f[i-3]+6-2*f[i-2]-2

    =3*g[i-1]+3*f[i-3]-2*f[i-2]+4

又因為-2*f[i-2]=-6*f[i-3]+2*f[i-4]

所以(將g[i]中的f[i-2]展開)

g[i]=3*g[i-1]+3*f[i-3]-6*f[i-3]+2*f[i-4]+4

    =3*g[i-1]-3*f[i-3]+2*f[i-4]+4

又因為:-g[i-2]=-3*f[i-3]+2*f[i-4]-2

所以:g[i]=3*g[i-1]-g[i-2]+2

g函數的初值:

g[1]=1、g[2]=5

這里是最終代碼(注意高精度!):

 

#include<iostream>
#include<cstdio>
using namespace std;
struct data{
       int a[101],len;
       };
int n;
data mul(data a,int k)
{
    for(int i=1;i<=a.len;i++)
            a.a[i]*=k;
    for(int i=1;i<=a.len;i++)
    {
            a.a[i+1]+=a.a[i]/10;
            a.a[i]%=10;
            }
    if(a.a[a.len+1]!=0)a.len++;
    return a;
} 
data sub(data a,data b)
{
    a.a[1]+=2;
    int j=1;
    while(a.a[j]>=10){a.a[j]%=10;a.a[j+1]++;j++;} 
    for(int i=1;i<=a.len;i++)
    {
           a.a[i]-=b.a[i];
           if(a.a[i]<0){a.a[i]+=10;a.a[i+1]--;}
    }
    while(a.a[a.len]==0)a.len--;
    return a;
}
int main()
{
    data f[101];f[1].a[1]=1;f[2].a[1]=5;
    f[1].len=f[2].len=1;
    scanf("%d",&n);
    for(int i=3;i<=n;i++)
            f[i]=sub(mul(f[i-1],3),f[i-2]);
    for(int i=f[n].len;i>0;i--)
       printf("%d",f[n].a[i]);
    return 0;
}
【BZOJ】1002:輪狀病毒

 

這里還有一種大犇的證明1002: [FJOI2007]輪狀病毒 (值得一看orz)

 

 

夢想總是要有的,萬一實現了呢?

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM