嘘~ 正在从服务器偷取页面 . . .

洛谷P4163 [SCOI2007]排列 题解


洛谷P4163 [SCOI2007]排列 题解

题目链接:P4163 [SCOI2007]排列

题意

给一个数字串 $s$ 和正整数 $d$ ,统计 $s$ 有多少种不同的排列能被 $d$ 整除(可以有前导 $0$)。例如 $123434$ 有 $90$ 种排列能被 $2$ 整除,其中末位为 $2$ 的有 $30$ 种,末位为 $4$ 的有 $60$ 种。

输入格式

输入第一行是一个整数 $T$,表示测试数据的个数,以下每行一组 $s$ 和 $d$,中间用空格隔开。$s$ 保证只包含数字 $0,1,2,3,4,5,6,7,8,9$。

输出格式

每个数据仅一行,表示能被 $d$ 整除的排列的个数。

数据范围

$s$ 的长度不超过 $10$,$1\le d\le 1000$,$1\le T\le 15$。

直接暴力不知道卡不卡得过,我感觉不行。

这道题我们可以把原字符串中的字符看作一个可重集然后状压dp。

设 $f_{S,j}$ 表示用了集合 $S$ 的数字,且此时模 $d$ 的值为 $j$ 时的方案数,则(下面的 $\uparrow$ 表示 +=

算重的问题很简单,组合数学嘛,只有把最后的 $f$ 除以「每个数的出现次数的阶乘的乘积」就好了

时间复杂度 $\mathcal{O}(2^{|s|}d)$

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
void up(int &x,int y) { x < y ? x = y : 0; }
void down(int &x,int y) { x > y ? x = y : 0; }
#define N ((int)())

char s[15];
int n,d,cnt[15],f[2098][1050],fac[15];
void clear()
{
    memset(cnt, 0, sizeof(cnt));
    memset(f, 0, sizeof(f));
}
void solve()
{
    cin >> s >> d; clear();
    for(n = 0; s[n]; n++) ++cnt[s[n] &= 15];
    int mx = 1 << n; f[0][0] = 1;
    for(int i = 0,ti,tj; i < mx; i++)
        for(int j = 0; j < d; j++) if(f[i][j])
            for(int k = 0; k < n; k++) if((ti = i | (1 << k)) != i)
                { tj = (j * 10 + s[k]) % d; f[ti][tj] += f[i][j]; }
    int c = 1; for(int i = 0; i < 10; i++) c *= fac[cnt[i]];
    cout << (f[mx - 1][0] / c) << '\n';
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    // freopen("check.in","r",stdin);
    // freopen("check.out","w",stdout);
    fac[0] = 1;
    for(int i = 1; i <= 11; i++) fac[i] = fac[i - 1] * i;
    int Q; cin >> Q; while(Q--) { solve(); }
    return 0;
}

参考文献

[1] https://yhx-12243.github.io/OI-transit/records/lydsy1072.html


文章作者: q779
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 q779 !
评论
  目录