1. 程式人生 > >[BZOJ]1005 明明的煩惱(HNOI2008)

[BZOJ]1005 明明的煩惱(HNOI2008)

sin -h 產生 word 答案 樹形 接下來 while 不同的

  BZOJ的第一頁果然還是很多裸題啊,小C陸續劃水屯些板子

Description

  自從明明學了樹的結構,就對奇怪的樹產生了興趣......給出標號為1到N的點,以及某些點最終的度數,允許在任意兩點間連線,可產生多少棵度數滿足要求的樹?

Input

  第一行為N(0 < N < = 1000),接下來N行,第i+1行給出第i個節點的度數Di,如果對度數不要求,則輸入-1.

Output

  一個整數,表示不同的滿足要求的樹的個數,無解輸出0.

Sample Input

  3
  1
  -1
  -1

Sample Output

  2

HINT

  兩棵樹分別為1-2-3;1-3-2.

Solution

  碰見這種沒有知識儲備腦子裏都沒有想法的題,考場上還是保佑自己碰到一些自己學過的算法吧。

  講這道題之前先來說說prufer編碼是什麽:

    ①prufer編碼是樹的一種表示形式,不同的編碼與不同的樹形態一一對應;

     (不同的樹形態指的是兩棵樹中至少有一條邊連接的點不同)

    ②根據定理證明,n個點最多能構成(n-2)^n種不同的樹形態。

     (至於為什麽是這個式子看看接下來prufer編碼是如何構造的就知道了)

    ③構造方法:

    技術分享

     如圖所示,為一棵有6個結點的樹,每次選出葉子節點中編號最小的一個,將與其相連的那個節點的標號加入數列,再將該葉子結點刪去。直到樹中剩下兩個節點為止。

     所以上圖的樹的prufer編碼就是:5 3 1 5(依次刪去2 4 3 1)。

     顯而易見,一棵節點數為n的樹的prufer編碼長度為n-2。

     由於prufer編碼的每一位都有可能是1~n,不同的prufer編碼有(n-2)^n種。

     所以根據第①條一一對應的性質,不同的樹有(n-2)^n種。

  利用prufer編碼,我們可以輕易地解決這道題。

  從prufer編碼中,我們可以看出一棵樹中所有點的度數,每個點的度數為它在prufer編碼中出現的次數+1。

  因此對於題目中規定度數的點,我們可以首先確定它們在prufer編碼中的位置。

  假設規定度數的點有p個,度數分別為a1、a2……ap。

  那麽把這p個點填進prufer編碼的方案數是技術分享。(排列組合、乘法原理瞎推)

  那麽prufer編碼中剩下的空位有技術分享個,未規定度數的節點有n-p個,所以方案數再乘上技術分享即可。

  由於答案沒有取模,所以要用到高精度乘/除單精度。

  題目中所說的無解情況有3種:

    ①技術分享

    ②技術分享

    ③技術分享

  時間復雜度寫得不是太糟都能過,註意高精度數的位數。

#include <cstdio>
#include <algorithm>
#include <cstring>
#define ll long long
#define mod 1000000000
#define MS 354
using namespace std;
struct hp
{
    int len; ll ar[MS];
    friend hp operator/(const hp& a,int b)
    {
        register ll lt=0;
        register int i;
        hp c;
        memset(&c,0,sizeof(c));
        c.len=a.len;
        for (i=a.len-1;i>=0;--i)
        {
            lt=lt*mod+a.ar[i];
            c.ar[i]=lt/b; lt%=b;
        }
        if (!c.ar[c.len-1]) --c.len;
        return c;
    }
    friend hp operator*(const hp& a,int b)
    {
        register int i,j;
        hp c;
        memset(&c,0,sizeof(c));
        c.len=a.len;
        for (i=0;i<a.len;++i)
        {
            c.ar[i]+=a.ar[i]*b;
            c.ar[i+1]+=c.ar[i]/mod;
            c.ar[i]%=mod;
        }
        if (c.ar[c.len]) ++c.len;
        return c;
    }
}sum;
int n,rn,uk;
 
inline int read()
{
    int n=0,f=1; char c=getchar();
    while (c<0 || c>9) {if(c==-)f=-1; c=getchar();}
    while (c>=0 && c<=9) {n=n*10+c-0; c=getchar();}
    return n*f;
}
 
int main()
{
    register int i,x;
    sum.ar[0]=1; sum.len=1;
    n=read(); rn=n-2;
    for (i=1;i<=rn;++i) sum=sum*i;
    for (i=1;i<=n;++i)
    {
        x=read()-1;
        if (x>0) {if (rn>=x) {for (rn-=x;x>=2;--x) sum=sum/x;} else return 0*printf("0");}
        else if (x==-2) ++uk;
        else return 0*printf("0");
    }
    if (rn&&!uk) return 0*printf("0");
    for (i=1;i<=rn;++i) sum=sum/i*uk;
    printf("%lld",sum.ar[sum.len-1]);
    for (i=sum.len-2;i>=0;--i) printf("%09lld",sum.ar[i]);
}

Last Word

  小C看到這道題的時候就覺得這肯定不是正常題,就是沒有看過相關的東西死都做不出來的那種。

  結果真的是這樣。

[BZOJ]1005 明明的煩惱(HNOI2008)