[BZOJ1072][SCOI2007]排列perm(状压dp)

题目描述

传送门

题解

状态:f[i][j]表示状态为i,余数为j的排列数。
转移:f[i|(1<<(j-1))][(k*10+a[j])%d]+=f[i][k];其中j不包含在状态i里。
目标:f[tot][0];

注意:这里处理有重复元素的全排列的方法:将所有的重复元素的全排列求出来,然后直接求整个的全排列,然后分别除以每一个重复元素的全排列。这样做是显然正确,因为每一种重复元素在整个里的贡献就是它自己的全排列,我们只保留一个。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=10;
const int D=1005;
char s[N];
int T,d,n,tot;
int a[N+1],f[1<<N][D],cnt[N+1],v[N+1];
inline void clear(){
    d=n=tot=0; memset(a,0,sizeof(a)); memset(f,0,sizeof(f));memset(cnt,0,sizeof(cnt));
    for (int i=0;i<=9;++i) v[i]=1;
}
int main(){
    scanf("%d",&T);
    while (T--){
        clear();
        cin>>s>>d; n=strlen(s); for (int i=1;i<=n;++i) a[i]=s[i-1]-'0';
        tot=(1<<n)-1;
        for (int i=1;i<=n;++i) 
          cnt[a[i]]++,v[a[i]]*=cnt[a[i]];
        f[0][0]=1;
        for (int i=0;i<=tot;++i)
          for (int k=0;k<d;++k)
            if (f[i][k])
              for (int j=1;j<=n;++j)
                if ((i&(1<<(j-1)))==0)
                  f[i|(1<<(j-1))][(k*10+a[j])%d]+=f[i][k];
        for (int i=0;i<=9;++i) f[tot][0]/=v[i]; 
        printf("%d\n",f[tot][0]);
    }
}

总结

刚开始的思路是错的,因为我无法存下所有排列的状态。
然后看了黄学长的思路:一维是余数。并且借鉴了解决重复元素问题的方法。
十分巧妙啊。

你可能感兴趣的:(dp,bzoj,SCOI)