1. 程式人生 > 實用技巧 >Luogu3223 [HNOI2012]排隊(排列組合)題解

Luogu3223 [HNOI2012]排隊(排列組合)題解

數學題

排列組合+高精度

前置芝士

思路

考慮容斥,老師不相鄰的方案數\(=\)不考慮老師特殊要求的方案數\(-\)保證老師相鄰的方案數

  • 不考慮老師限制,即老師與男生相同,則方案數為:\(A(n+3,m)\times A(n+2,n+2)\)
  • 保證老師相鄰,即把兩個老師捆在一起,看做一個男生,則方案數為:\(A(n+2,m)\times A(n+1,n+1)\times A(2,2)\)

由於\(n\)\(m\)都很小,可以直接高精度暴力計算排列。

注意:壓位高精記得要控制輸出位數!

程式碼

#include <cstdio>
#define LL long long

using namespace std;

const int maxn = 1e4 + 10;
const LL p = 10000000000ll;
int len,n,m,len1;
LL ans[maxn],f[maxn];

void tim(int x){
    LL tmp = 0, pre = 0;
    for(int i = 1; i <= len; ++ i){
        tmp = (LL)x * ans[i];
        ans[i] = tmp % p + pre;
        pre = tmp / p;
    }
    if(pre) len++, ans[len] = pre;
}

void tim1(int x){
    LL tmp = 0, pre(0);
    for(int i = 1; i <= len1; ++ i){
        tmp = (LL)x * f[i];
        f[i] = tmp % p + pre;
        pre = tmp / p;
    }
    if(pre) len1++, f[len1] = pre;
}

int cnt(LL x){
    int num = 0;
    while(x) num++, x /= 10;
    return num;
}

void re(){
    LL pre = 0;
    for(int i = 1; i <= len; ++ i){
        if(!pre && i > len1) break;
        ans[i] -= pre; pre = 0;
        if(ans[i] < f[i]) ans[i] = ans[i] + (p - f[i]), pre = 1;
        else ans[i] = ans[i] - f[i];
    }
    while(!ans[len] && len){
        len--;
        if(!len) break;
    } 
    printf("%lld", ans[len]);
    for(int i = len - 1; i >= 1; -- i){
        printf("%010lld", ans[i]);
    }
    printf("\n");
}

int main(){
    scanf("%d%d", &n, &m);
    ans[++len] = 1; f[++len1] = 1;
    for(int i = n + 3 - m + 1; i <= n + 3; ++ i) tim(i); 
    for(int i = n + 2 - m + 1; i <= n + 2; ++ i) tim1(i);
    for(int i = 2; i <= n + 2; ++ i){
        tim(i);
        if(i <= n + 1) tim1(i);
    }
    tim1(2);
    re();
    //printf("%lld\n", p);
    return 0;
}