1. 程式人生 > >SCOI2007 排列

SCOI2007 排列

tchar per 就是 clu == \n sizeof math 包含

傳送門

這道題竟然可以使用全排列暴力模擬水過……

不過我們還是說一下正解。既然數據範圍這麽小,所以我們考慮狀壓DP。

用dp[i][j]表示狀態為i時,當前選取的所有數的排列,其對d取模後結果為j有多少種情況。其中i是一個二進制數字串,每一個二進制位對應原數組中的數字有沒有被選中。

簡單的解釋一下,假設原數組中是1246,那麽當狀態為0011時,我們相當於求4,6這兩個數字組成的全排列,對d取模後結果為j有多少種情況。

DP方程如下,若(i & 1<<k) == 0 ,則dp[i | (1 << k)][(j * 10 + f[k]) % d] += dp[i][j]

這裏解釋一下,其實就相當於我們在每次dp轉移的時候又取了一個數,並且把取得這個數加到末尾,計算一共有多少種排列對d取模之後結果為j。

比如說(原數組還是用上面的),從狀態1010轉移至1011就相當於是把1,4的全排列末尾加上6,之後計算。

有人可能會有疑問,你要算的是當前選取的所有數(1,4,6)的全排列可能產生的方案數,而你當前只計算了6在末尾的情況,它在中間的情況你並沒有計算。

其實並不是這樣。對於狀態1011,它可以從不止一個狀態中轉移過來。比如0011,1010,1001.而這三種狀態其實恰好就對應了排列1,4,排列4,6,排列1,6,把新加入的數分別放在他們的後面,當前選取的三個數的全排列還是會被完全考慮到的。

同理,對於任意一種已經被轉移的狀態,其必然已經計算過當前選取的所有數字的全排列的情況,所以也就必然能保證所有情況都被枚舉到。

再說的通俗一點,拿上面的舉例。比如狀態1010,他可以由1000和0010轉移,所以狀態1010必然已經包含過前面兩種狀態構成的所有情況,當他繼續向後DP的時候亦然。

還有一點比較顯然,我們直接把上一次取模的結果*10加上當前數再取模即可,因為他和原數肯定是同余的。

這樣dp方程的正確性就很顯然了。

註意應該怎麽dp,初始值dp[0][0] = 1.然後註意dp的時候要從0開始,不要取錯。

還有就是遇到了一個有重復元素的情況。比如說122.122被轉移的時候,可以從狀態110,101,011轉移過來。而前兩種狀態計算的是重復的。再類推一下,可以得到,每個重復的元素會貢獻其出現次數的階乘倍的多余答案,應該被除去。

這樣就可以了。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<queue>
#include<cstring>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar(‘\n‘)
using namespace std;
typedef long long ll;
const int M = 15;
ll n,L,f[M],dp[2000][2000],num[M],t,d,len,sum,cur,ans; 
ll read()
{
    ll ans = 0,op = 1;
    char ch = getchar();
    while(ch < 0 || ch > 9)
    {
    if(ch == -) op = -1;
    ch = getchar();
    }
    while(ch >=0 && ch <= 9)
    {
    ans *= 10;
    ans += ch - 0;
    ch = getchar();
    }
    return ans * op;
}
char s[M];
int main()
{
    t = read();
    while(t--)
    {
    memset(dp,0,sizeof(dp));
    memset(f,0,sizeof(f));
    memset(s,0,sizeof(s));
    memset(num,0,sizeof(num));
    scanf("%s",s+1);
    len = strlen(s+1);
    rep(i,1,len) f[i] = s[i] - 0,num[f[i]]++;
    d = read();
    dp[0][0] = 1;
    rep(i,0,(1<<len)-1)
        rep(j,0,d-1)
        rep(k,0,len-1)
        if(!(i & (1<<k))) dp[i|(1<<k)][(j*10+f[k+1])%d] += dp[i][j];
    ans = dp[(1<<len)-1][0];
    rep(i,0,9)
        while(num[i] > 1) ans /= num[i],num[i]--;
    printf("%lld\n",ans);
    }
    return 0;
}

SCOI2007 排列