zoukankan      html  css  js  c++  java
  • 密码

    题意:

    给出一个长度不超过17的数字num,然后排列这个数字所有位数上的数,使得新的数能被17整除,求这新的数中第K(<=17!)小的数。

    题解:

    既然要求出第K小,那么可以从高位到低位一位一位寻找,但是怎么寻找呢?可以计算出从高位到低位能被17整除的方案数,如果大了就进行下一位,小了就变大当前的这一位。

    现在的问题就是求出能被17整除的方案数,数据范围很小考虑状态压缩,对于dp[S]表示已经选了所给数的状态为S的位置的数了,但是现在并没法转移因为有余数的限制那么再加一维余数好了,dp[r][S]表示已经选了所给数的状态为S的位置的数组成的新数对17取模为r的方案数。

    状态转移 就是将所有状态的后继求和。

    代码:

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
     
    const int N = 20;
    const int mod = 17;
    #define LL long long
    LL dp[20][1<<18], pow[N];
    int vis[20][1<<18], n;
    char num[N];
    LL K;
     
    LL calc (int r, LL S, int pos) {
        if (vis[r][S]) return dp[r][S];
        vis[r][S] = 1;
        if (S == 0) {
            if (r == 0) return dp[r][S] = 1;
            else return dp[r][S] = 0;
        }
        int begin = (pos == n) ? 1 : 0;
        for (int i = begin; i <= 9; ++i) {
            for (int j = 0; j < n; ++j) {
                if ((1LL << j) & S && num[j] - '0' == i) {
                    int val = ((LL)r + (LL)i * pow[pos-1]) % mod;
                    if (val < 0) val += mod;
                    dp[r][S] += calc (val, S ^ (1 << j), pos - 1);
                    break;
                }
            }
        }
        return dp[r][S];
    }
     
    void print (int r, LL S, int pos, LL k) {
        if (S == 0) return;
        int begin = (pos == n) ? 1 : 0;
        for (int i = begin; i <= 9; ++i) {
            for (int j = 0; j < n; ++j) {
                if ((1LL << j) & S && num[j] - '0' == i) {
                    int val = ((LL)r - (LL)i * pow[pos-1]) % mod;
            if (val < 0) val += mod;
                    if (k <= dp[val][S ^ (1 << j)]) {
                        printf ("%d",i);
                        print(val, S ^ (1<<j), pos - 1, k);
                        return;
                    }
                    else k -= dp[val][S ^ (1 << j)];
                }
            }
        }
    }
     
    int main () {
        cin >> num >> K;
        n = strlen (num);
        pow[0] = 1;
        for (int i = 1; i <= n; ++i) pow[i] = pow[i-1] * 10;
        calc (0, (1 << n) - 1, n);
        print (0, (1 << n) - 1, n, K);
        return 0;
    }
    

      

      

      

    总结:

    有点像名次树的感觉~求这种有方向性(高位到低位,名次树就是大小)的第K小,一般会采用计算方案数的方法,看到数据这么小!怎么也要状压才对得起这个数据,对吧?

  • 相关阅读:
    [BZOJ1584][Usaco2009 Mar]Cleaning Up 打扫卫生
    CSS浮动
    Django by example -----1总结
    C#函数重载
    linux目录的特点
    Linux调优
    linux
    对齐方式
    19-10-25-G-悲伤
    19-10-24-H
  • 原文地址:https://www.cnblogs.com/xgtao/p/6001993.html
Copyright © 2011-2022 走看看