P4163 [SCOI2007]排列

Problem

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

|s|101d10001t15

Input

第一行一个整数 t,表示数据组数。
接下来 t 行,每行一个数字串 s 和一个整数 d

Output

每组数据一行一个整数表示答案。

Sample

Input 1

7
000 1
001 1
1234567890 1
123434 2
1234 7
12345 17
12345678 29

Output 1

1
3
3628800
90
3
6
1398

Solution

观察到 |s| 很小,可以尝试状压。

定义 fi,j 表示当前选数集合为 i,形成的数模 dj 的方案数。转移方程不难写出:

f[i|1<<k][(j×10+ak)%d]=f[i|1<<k][(j×10+ak)%d]+f[i][j]

但直接转移会计算重复,因为 s 中会有相同的元素。

又因为 |s|10,故每次转移时标记下即可。

代码:

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;

const int kmax = 1e3 + 3;
const int kmaxM = 13;

int a[kmax];
bool b[kmax];
char c[kmax];
int t, n, d;
long long f[1 << kmaxM][kmax];

void Solve() {
  scanf("%s %d", c, &d);
  n = strlen(c);
  for (int i = 0; i < n; i++) {
    a[i] = c[i] - '0'; 
  }
  memset(f, 0, sizeof(f));
  f[0][0] = 1;
  for (int i = 0; i < 1 << n; i++) {
    memset(b, 0, sizeof(b)); // 清空标记
    for (int j = 0; j < n; j++) {
      if (i & (1 << j)) continue;
      if (b[a[j]]) continue; // 标记过,不要重复计算
      b[a[j]] = 1; // 添加标记
      for (int k = 0; k < d; k++) {
        f[i | (1 << j)][(k * 10 + a[j]) % d] += f[i][k]; // 转移
      }
    }
  }
  printf("%lld\n", f[(1 << n) - 1][0]);
}

int main() {
  scanf("%d", &t);
  while (t--) { // 多组数据
    Solve();
  }
  return 0;
}

posted @   ereoth  阅读(24)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示