[数位DP][状压DP]密码

本文介绍了一种算法,用于寻找给定字符串中能被17整除的最小字典序数。通过排序和状态压缩技术,实现了高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

在又一次消灭林登·万的战斗中,指挥官moreD缴获了一个神奇的盒子。盒子异常的坚固,以至于完全无法摧毁,唯一打开的方式是通过盒上的密码锁。
经过仔细的调查,研究人员一致认为这个盒子中隐藏了林登·万和他的弟弟林登·图的秘密。然而moreD使用了许多办法,都没能打开这个盒子。最后只好将这个盒子封存在了仓库的底层。
事情并没有结束。moreD之所以没能打开这个盒子,是因为老牌的调查员/邪教徒LCJ隐瞒了它的调查结果。LCJ经过不懈的努力,得出了结论。即:给你一个长度不超过17的由0~9组成的无前导0的字符串S,S中的数字排列组成的无前导零的能被17整除的整数中字典序第K小的那个数就是密码。
尽管解开了密码,然而处于对未知的恐惧,LCJ最终并没有打开盒子。然而另一个资历较浅的调查员/邪教徒,你,YDMan不知通过什么办法得知了上述信息,并得到了S和K。现在你决定要解开这个密码,来取得“终极的智慧”。

Input
一行,一个由0~9组成的字符串S和一个不超过17!的正整数K。

Output
一行,即密码。数据保证有解。

Sample Input
输入1:
17 1

输入2:
2242223 2

Sample Output
输出1:
17

输出2:
2242232

Data Constraint
对于40%的数据,字符串S长度<=12,K<=2*10^6。
对于100%的数据,字符串S长度<=17,K<=17!。

后记:

事实上,盒子中装的正是伦道夫·卡特当年穿越银匙之门的银色钥匙。YDMan在“掌(bei)握(xia)智(huai)慧(le)”智慧,终于成为了一个伟大的“诗(huan)人(zhe)”。而林登·万则和和他的弟弟继续使用林登·图钥匙与指挥官moreD进行不屈不挠的斗争。

分析

其实第一眼看状压第二眼看数位(可我怎么没想到结合起来呢?)
我们因为要字典序,所以给数位小到大排序
然后我们用二进制状压表示选或不选该数位,另一个表示结合产生的余数为0~16的方案数
然后我们只要枚举状态从满状态中减去即可,最后输出没被选上的。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <memory.h>
#include <algorithm>
#define rep(i,a,b) for (i=a;i<=b;i++)
typedef long long ll;
const ll MOD=17;
using namespace std;
int a[18];
ll fact[18],k,
pow[18]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536,131072},
p10[18],f[1<<17][17];
int len,n;
char s[20];
int main() {
    int i,j,l;
    scanf("%s",&s);
    scanf("%lld",&k);
    len=strlen(s)-1;
    rep(i,0,len) a[i]=s[i]-'0';
    sort(a,a+len+1);
    n=(1<<len+1)-1;
    f[0][0]=1;
    rep(i,0,n-1) {
        int w=1;
        rep(j,0,len)
        if (i&pow[j]) w=w*10%MOD;
        rep(j,0,len)
        rep(l,0,16)
        if (!(i&pow[j])) f[i|pow[j]][(l+a[j]*w%MOD)%MOD]+=f[i][l];
    }
    fact[0]=1;
    rep(i,1,17) fact[i]=fact[i-1]*i;
    rep(i,0,n-1) {
        int c[10];
        memset(c,0,sizeof(c));
        rep(j,0,len) if (i&pow[j]) c[a[j]]++;
        rep(j,0,9) rep(l,0,16) f[i][l]/=fact[c[j]];
    }
    int s=n,p=0;
    p10[0]=1;
    rep(i,1,17) p10[i]=p10[i-1]*10%MOD;
    rep(i,0,len) {
        int last=-1;
        rep(j,0,len)
        if (s&pow[j]) {
            if (a[j]==last) continue;
            last=a[j];
            if (!i&&!a[j]) continue;
            if (f[s^pow[j]][(17-(p+a[j]*p10[len-i])%MOD)%MOD]>=k) {
                s^=pow[j];
                p=(p+a[j]*p10[len-i])%MOD;
                printf("%d",a[j]);
                break;
            }
            else k-=f[s^pow[j]][(17-(p+a[j]*p10[len-i])%MOD)%MOD];
        }
    }
    rep(i,0,len)
    if (s&pow[i]) printf("%d",a[i]);
}
### 关于缩动态规划(DP)的相关题目与教程 #### 洛谷平台上的资源推荐 洛谷作为一个优质的在线编程练习网站,提供了大量有关缩动态规划的学习资料和实战题目。对于想要深入理解并掌握这一复杂算法的人来说,这些资源非常有价值。 #### 推荐的经典入门题库 - **P1433 吃奶酪**:这道题不仅涉及到了深度优先搜索加缩的思想,还融合了动态规划的概念,在解决过程中能够很好地锻炼选手对多种算法组合运用的能力[^2]。 - **PRZ - POI2004**:此问题同样是一道典型的缩动态规划应用实例,通过该题目的训练可以帮助加深对这类问题处理方法的理解[^3]。 #### 学习路径建议 为了更好地理解和实践缩动态规划,建议按照如下顺序逐步推进: - 熟悉二进制位运算操作的基础知识,这是实现高效编码的关键技能之一; - 复习基本的动态规划理论及其常见应用场景; - 结合具体案例研究如何定义合适的态表示形式以及设计有效的态转移方程; ```python def dp_solution(): """ 这里提供了一个简化版的缩动态规划框架, 实际编写时需根据具体问题调整参数设置及逻辑流程。 """ n = ... # 输入规模大小 state_size = 1 << n # 总共可能存在的不同态数 # 初始化记忆化数组,默认值设为无穷大或其他不可能取到的最大/最小边界条件 memo = [-1] * state_size def dfs(current_state, current_index): nonlocal memo if all_bits_set(current_state): return cost_to_end[current_index] if memo[current_state] != -1: return memo[current_state] min_cost = float('inf') for next_index in range(n): new_state = update_state_with_next_move(current_state, next_index) transition_cost = calculate_transition_expense(current_index, next_index) total_cost = transition_cost + dfs(new_state, next_index) min_cost = min(min_cost, total_cost) memo[current_state] = min_cost return min_cost result = dfs(initial_state(), start_position()) return result ``` 上述代码片段展示了利用函数`dfs()`来进行带备忘录的记忆化搜索过程,其中包含了几个重要的组成部分如初始化全局变量、判断终止条件、剪枝优化等技巧来提高求解效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值