洛谷P4163 [SCOI2007]排列

@Pelom  October 12, 2021

题意

给一个数字串$s$和正整数$d$, 统计$s$有多少种不同的排列能被$d$整除

数据范围:$s$的长度$\le 10,1 \le d \le 1000$

题解

直接计算整除的较为麻烦,可以状压使用过的数$d$并记录当前数模$d$的余数,再来表示方案数
用$f[i][j]$表示选用过的数状态压缩为$i$,当前余数为$j$
易得状态转移方程$$f[i|(1<<j)][(k*10+a[j])\%d]=sum(f[i][k])$$其中$j$为不在$i$中的数,$k$为余数
但是若有重复数字会多次计算,我们记下这个重复的次数$cnt$,那么答案需要除以$A_{cnt}^{cnt}=cnt!$

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int D=1000+10;
int t,l,d;
char s[10];
int a[10];
int ans;
int fac[10],cnt[10],dp[1<<11][D];
int main(){
    scanf("%d",&t);
    fac[0]=1;
    for(int i=1;i<10;i++)
        fac[i]=fac[i-1]*i;
    for(;t--;){
        memset(cnt,0,sizeof(cnt));
        memset(dp,0,sizeof(dp));
        scanf("%s%d",s,&d);
        l=strlen(s);
        for(int i=0;i<l;i++)
            cnt[a[i]=s[i]-'0']++;
        dp[0][0]=1;
        for(int i=0;i<(1<<l);i++)
            for(int j=0;j<l;j++){
                if(i&(1<<j))
                    continue;
                for(int k=0;k<d;k++)
                    dp[i|(1<<j)][(k*10+a[j])%d]+=dp[i][k];
            }
        ans=dp[(1<<l)-1][0];
        for(int i=0;i<10;i++)
            ans/=fac[cnt[i]];
        printf("%d\n",ans);
    }
    return 0;
}

添加新评论