[SCOI2007]排列

[SCOI2007]排列

题意:

给一个数字串 \(s\) 和正整数 \(d\), 统计 \(s\) 有多少种不同的排列能被 \(d\) 整除,可以有前导 \(0\)

分析:

看最多只有 \(10\) 位,我们考虑状压:

\(dp[S][i]\) 表示枚举到集合为 \(S\) 时形成的数,模 \(d\)\(k\) 的情况,则有

首先,我们枚举所有的集合 \(S\) ,然后再枚举所有没有被选的数 \(j\) ,再枚举余数 \(k\) 即可转移。

转移方程为:

dp[S|1<<(j-1)][(k*10+a[j])%d]+=dp[S][k];//添加进去这个数

考虑重复排列,因为当前要填的数字,很有可能要填好几遍,所以用一个 \(vis\) 数组判断当前数有没有选择。

然后直接顺着枚举就行了

#include<bits/stdc++.h>
using namespace std;
const int N=12,M=1500;
int T,d,a[N],cnt,dp[M][M];
bool vis[N];
char s[N];
void init(){
    memset(dp,0,sizeof(dp)); dp[0][0]=1;
    cnt=0;
}
int main(){
    cin>>T;
    while(T--){
        init();
        scanf("%s%d",s+1,&d);
        int len=strlen(s+1); 
        for(int i=1;i<=len;i++) a[i]=s[i]-'0';
        for(int S=0;S<(1<<len)-1;S++){//考虑每一种情况
            memset(vis,0,sizeof(vis));
            for(int j=1;j<=len;j++)
                if(!(S&(1<<(j-1)))&&!vis[a[j]]){//没有转移过,而且没有选择
                    vis[a[j]]=1;
                    for(int k=0;k<d;k++)//k表示对d取余的数  
                        dp[S|1<<(j-1)][(k*10+a[j])%d]+=dp[S][k];//添加进去这个数
                }
        }
        printf("%d\n",dp[(1<<len)-1][0]);
    }
    system("pause");
    return 0;
}
posted @ 2021-10-26 17:36  Evitagen  阅读(61)  评论(0)    收藏  举报