• SCOI2007 排列


    传送门

    这道题竟然可以使用全排列暴力模拟水过……

    不过我们还是说一下正解。既然数据范围这么小,所以我们考虑状压DP。

    用dp[i][j]表示状态为i时,当前选取的所有数的排列,其对d取模后结果为j有多少种情况。其中i是一个二进制数字串,每一个二进制位对应原数组中的数字有没有被选中。

    简单的解释一下,假设原数组中是1246,那么当状态为0011时,我们相当于求4,6这两个数字组成的全排列,对d取模后结果为j有多少种情况。

    DP方程如下,若(i & 1<<k) == 0 ,则dp[i | (1 << k)][(j * 10 + f[k]) % d] += dp[i][j]

    这里解释一下,其实就相当于我们在每次dp转移的时候又取了一个数,并且把取得这个数加到末尾,计算一共有多少种排列对d取模之后结果为j。

    比如说(原数组还是用上面的),从状态1010转移至1011就相当于是把1,4的全排列末尾加上6,之后计算。

    有人可能会有疑问,你要算的是当前选取的所有数(1,4,6)的全排列可能产生的方案数,而你当前只计算了6在末尾的情况,它在中间的情况你并没有计算。

    其实并不是这样。对于状态1011,它可以从不止一个状态中转移过来。比如0011,1010,1001.而这三种状态其实恰好就对应了排列1,4,排列4,6,排列1,6,把新加入的数分别放在他们的后面,当前选取的三个数的全排列还是会被完全考虑到的。

    同理,对于任意一种已经被转移的状态,其必然已经计算过当前选取的所有数字的全排列的情况,所以也就必然能保证所有情况都被枚举到。

    再说的通俗一点,拿上面的举例。比如状态1010,他可以由1000和0010转移,所以状态1010必然已经包含过前面两种状态构成的所有情况,当他继续向后DP的时候亦然。

    还有一点比较显然,我们直接把上一次取模的结果*10加上当前数再取模即可,因为他和原数肯定是同余的。

    这样dp方程的正确性就很显然了。

    注意应该怎么dp,初始值dp[0][0] = 1.然后注意dp的时候要从0开始,不要取错。

    还有就是遇到了一个有重复元素的情况。比如说122.122被转移的时候,可以从状态110,101,011转移过来。而前两种状态计算的是重复的。再类推一下,可以得到,每个重复的元素会贡献其出现次数的阶乘倍的多余答案,应该被除去。

    这样就可以了。

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<algorithm>
    #include<queue>
    #include<cstring>
    #define rep(i,a,n) for(int i = a;i <= n;i++)
    #define per(i,n,a) for(int i = n;i >= a;i--)
    #define enter putchar('
    ')
    using namespace std;
    typedef long long ll;
    const int M = 15;
    ll n,L,f[M],dp[2000][2000],num[M],t,d,len,sum,cur,ans; 
    ll read()
    {
        ll ans = 0,op = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9')
        {
        if(ch == '-') op = -1;
        ch = getchar();
        }
        while(ch >='0' && ch <= '9')
        {
        ans *= 10;
        ans += ch - '0';
        ch = getchar();
        }
        return ans * op;
    }
    char s[M];
    int main()
    {
        t = read();
        while(t--)
        {
        memset(dp,0,sizeof(dp));
        memset(f,0,sizeof(f));
        memset(s,0,sizeof(s));
        memset(num,0,sizeof(num));
        scanf("%s",s+1);
        len = strlen(s+1);
        rep(i,1,len) f[i] = s[i] - '0',num[f[i]]++;
        d = read();
        dp[0][0] = 1;
        rep(i,0,(1<<len)-1)
            rep(j,0,d-1)
            rep(k,0,len-1)
            if(!(i & (1<<k))) dp[i|(1<<k)][(j*10+f[k+1])%d] += dp[i][j];
        ans = dp[(1<<len)-1][0];
        rep(i,0,9)
            while(num[i] > 1) ans /= num[i],num[i]--;
        printf("%lld
    ",ans);
        }
        return 0;
    }
  • 相关阅读:
    Git——快速安装Git及初始化配置【二】
    Git——Git的简单介绍【一】
    web scraper——简单的爬取数据【二】
    web scraper——安装【一】
    PHP——实现随机打乱一个二维数组
    Vue——显示微信用户名称中enjoin表情
    Vue——轻松实现vue底部点击加载更多
    微信报错——10003
    Java_myBatis_逆向工程
    Java_myBatis_XML代理_动态SQL
  • 原文地址:https://www.cnblogs.com/captain1/p/9539613.html
Copyright © 2020-2023  润新知