题目连接:https://codeforces.com/contest/1238/problem/E
题目大意:
给你一个长度为 \(n(n \le 10^5)\) 的字符串s和 \(m(m \le 20)\) ,这个字符串由前 \(m\) 个小写字母组成。
如今你要找一个前 \(m\) 个字符的一个排列p,在这个排列p的基础上生成字符串s,并计算总代价。
代价的计算过程是:
好比我如今已经生成了字符串s的前i个字符 \(s_{1..i}\) ,如今我要生成第i+1个字符 \(s_{i+1}\) ,而且我假设 \(s_i\) 对应的字符是x,\(s_{i+1}\) 对应的字符是y,而且字符x在排列p中的位置是 \(pos_x\) ,字符y在排列p中的位置是 \(pos_y\) ,那么生成了字符 \(s_{i+1}\) 以后,总代价增长了 \(| pos_x - pos_y |\) 。
你须要找到全部排列方案中总代价最小的那个方案所对应的最小总代价。c++
题目分析:
首先能够看一下 官方题解:
函数
官网的解释当中涉及到了一个“subset dynamic programming”,我粗略地将它翻译为“子集动态规划”,其实能够发现这道题目的1真的跟子集有一些练习。
同时它跟状态压缩也有一些关系。
咱们用i来表示每个状态(\(0 \le i \lt 2^m\)),这个i其实对应成一个二进制数就是一个 m 位的二进制数,若是i的第j位为1就说明这个状态中已经放了第j个字符,不然就说明没有放第j个字符,那么咱们就能够放第j个字符,也就是说:
经过状态 i
和字符 j
可以扩展出一个新的状态 i | (1<<j)
。
并且 j
放的位置也是肯定的——咱们能够用函数 __builtin_popcount(i)
来获取 i 的二进制表示种存在多少位为1。
而后咱们这里我以为最重要的一个点也是困扰了我好久的一个点是—— “如何消除位置的影响” 。
其实对于每个状态 i
和字符 j
,若是想要从状态 i
转移到状态 i | (1<<j)
,而且咱们假设 i
的二进制表示中有 c 位为1,那么咱们其实能够发现,j所处的排列的位置是肯定的,那就是 c (也能够是c+1,这个视你的初始坐标决定,咱们这里就假设为c了)。
可是到目前为止咱们还不能消除距离的影响。
咱们能够枚举状态i里面的第k位:ui
因此,咱们能够发现,若是我令 \(f[i]\) 表示i这个状态的最小总代价,
那么,当咱们当前判断状态i和字符j的时候(而且咱们假设i的第j位为0,由于此时咱们能够将状态 i
加上字符 j
变到一个新的状态 i | (1<<j)
)。
咱们一开始开一个变量 \(tmp = f[i]\) ,而后从 0 到 m-1 去遍历字符k:spa
那么这样为何消除了位置的影响呢?
咱们假设如今放j的时候尚未放k,则咱们的tmp变量减去了 \(cnt[k][j] \times c_j\) ,那么到以后去放k时候,咱们的tmp变量还会加上 \(cnt[k][j] \times c_k\) ,而 \(c_k - c_j\) 其实就是它们的距离,这么一加一减就间接地处理了距离(这个点困惑了我好长时间,直到豁然开朗!)。翻译
而后对于每个状态i和字符j(要求状态i中的第j位为0),以及计算获得的tmp:code
f[ i | (1<<j) ] = max(f[ i | (1<<j) ] , tmp);
实现代码以下:blog
#include <bits/stdc++.h> using namespace std; int n, m, f[(1<<20)], cnt[20][20]; string s; int main() { cin >> n >> m >> s; for (int i = 1; i < n; i ++) { int a = s[i-1] - 'a', b = s[i] - 'a'; if (a != b) { cnt[a][b] ++; cnt[b][a] ++; } } fill(f+1, f+(1<<m), INT_MAX); for (int i = 0; i < (1<<m)-1; i ++) { int c = __builtin_popcount(i); for (int j = 0; j < m; j ++) { if ( !( i & (1<<j) ) ) { int tmp = f[i]; for (int k = 0; k < m; k ++) { if (i & (1<<k)) { tmp += cnt[j][k] * c; } else { // 由于预处理cnt的时候保证cnt[x][x]==0 tmp -= cnt[j][k] * c; } } f[ i | (1<<j) ] = min(f[i | (1<<j)], tmp); } } } cout << f[ (1<<m)-1 ] << endl; return 0; }