Codeforces (div2 E)1238E Keyboard Purchase(状态压缩DP)

题目要求找到一个字母排列的键盘,使得输入特定密码时手指移动的总距离最小。密码长度为10^5,由前20个小写字母组成。使用状态压缩动态规划进行求解,预处理相邻字母出现的次数,然后计算将字母集合作为键盘左侧时,右侧所有不在集合内的字母在一点的最小总距离。转移方程涉及将一个字母加入集合对现有状态的影响,即所有元素左移一位时增加的总距离。
摘要由CSDN通过智能技术生成

题意:有一串密码你需要经常输入,长度为n(1e5)由前m(20)个小写字母组成。你需要买一个键盘即m个字母的排列,当你在输入该密码的时候,你手指(单指打字)移动的总距离最小。移动的距离就是密码每相邻2个字母在键盘上的距离总和。问你最小距离。
思路:
首先预处理d[i][j]为i和j在该密码相邻的次数。
dp[se]表示当集合se在键盘的左侧(不考虑se的顺序,只考虑某字母有没有在se里面)其他不在该集合的字母在右侧且在一个点上。当m为6(即字符集为abcdef时),se为{a,b,c}时,dp[se]代表abc在位置1,2,3上,def全部在4位置上。这样一个键盘的最小总距离。
现在考虑转移方程,在求dp[se]时假设是通过dp[se1]转移而来(size(se1) =size(se)-1);设se为{a,b,c,d},se1为{a,b,c};相当于把d放入集合时对改状态做了多少贡献,即集合se的元素全部左移一位,贡献就是sum(d[i][j])(i代表在se的元素,j即为不在se的元素)。
代码

#include <algorithm>
#include <string>
#include <cstring>
#include <cstdio>

using namespace std;

#define sz(a) a.size()
#define all(x) (x).begin(), (x).end()
#define endl '\n'
#define debug(a) cout<<#a<<": "<<a<<'\n'
#define mod(x) (((x)%MOD+MOD)%MOD)
#define mem(a, b) memset(a,b,sizeof(a))
typedef long long ll;
typedef unsigned long long ull;

const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const int maxn = 3e5 + 7;

char str[maxn];
int d[30][30];
int dp[1<<20];

int main()
{
//    freopen("in.txt", "r", stdin);
    int n,m;
    scanf("%d%d",&n,&m); scanf("%s",str);

    for(int i = 1; i<n; i++)
        d[str[i]-'a'][str[i-1]-'a']++, d[str[i-1]-'a'][str[i]-'a']++;

    memset(dp,INF,sizeof(dp)); dp[0] = 0;
    
    for(int se = 1; se<(1<<m); se++)
    {
        for(int i = 0; i<m; i++) if(se&(1<<i))
                dp[se] = min(dp[se^(1<<i)],dp[se]);

        for(int i = 0; i<m; i++) if(se&(1<<i))
            for(int j= 0; j<m; j++) if(!(se&(1<<j)))
                dp[se] += d[i][j];
    }
    printf("%d\n", dp[(1<<m)-1]);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值