bzoj1072Perm——状压DP

题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1072

数字串最多只有10位,所以考虑用状压;

压缩的状态是哪些数字被用过,这样可以从一种状态加一个数字转移到另一种状态;

求能整除一个数的个数,那么当然记录当前状态下mod该数的余数,则在一种状态末尾加上另一个数时,这一维的转移就可以方便地表示;

然而因为把每个数看做是不同的,所以这样对于重复出现的数则计算了多次:

比如有两个1,则所有排列中这两个1都会交换位置被算两遍,所以最终答案要/2;

也就是这么多个数的全排列,它的阶乘,所以最后答案要除以所有这些阶乘来去重;

注意循环的内外层关系。

这位的博客写得颇为详细:https://blog.csdn.net/wzq_qwq/article/details/46366399

代码如下:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int t,d,a[15],f[1500][1005],up,s[15];
int jc[15]={1,1,2,6,24,120,720,5040,40320,362880,3628800};
char dc[15];
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        memset(s,0,sizeof s);
        memset(f,0,sizeof f);
        cin>>dc;
        int ln=strlen(dc);
        for(int i=1;i<=ln;i++)
        {
            a[i]=dc[i-1]-'0';
            s[a[i]]++;
        }
        scanf("%d",&d);
        up=(1<<ln)-1;
        f[0][0]=1;
        for(int j=0;j<=up;j++)//顺序 
            for(int l=0;l<d;l++)
            {
                if(!f[j][l])continue;//剪枝 
                for(int i=1;i<=ln;i++)
                    if((j&(1<<(i-1)))==0)
                    {
                        int k=j|(1<<(i-1));
                        f[k][(l*10+a[i])%d]+=f[j][l];
                    }
            }
        for(int i=0;i<=9;i++)
            if(s[i])f[up][0]/=jc[s[i]];
        printf("%d\n",f[up][0]);
    }
    return 0;
}

 

posted @ 2018-03-30 00:33  Zinn  阅读(150)  评论(0编辑  收藏  举报