当前位置: 代码迷 >> 综合 >> Educational CF Round 74 (Div. 2)___E. Keyboard Purchase —— 子集划分dp + 状压
  详细解决方案

Educational CF Round 74 (Div. 2)___E. Keyboard Purchase —— 子集划分dp + 状压

热度:17   发布时间:2024-01-09 10:53:46.0

题目链接:点我啊╭(╯^╰)╮

题目大意:

    长度为 n ≤ 1 e 5 n≤1e5 n1e5 的串,字符集为 m ≤ 20 m≤20 m20
    键盘上有对应的 m m m 个键,排成一排
    键位自定义,问输出这个串的最短总和距离为多少??

解题思路:

     m m m 只有 20 20 20, 考虑状压
    设 c n t [ x ] [ y ] cnt[x][y] cnt[x][y] 为字母 x x x y y y的总次数
     d p [ s ] dp[s] dp[s] 为状态为 s s s 时,已经将 s s s 每个状态位的字符放到键盘上,移动的最少距离
    接下来考虑距离的影响

    考虑每次转移时,新的字母 x x x 放到键盘的最右边,设 p o s [ x ] pos[x] pos[x] x x x 在键盘上的位置
    则从 d p [ s ] ? > d p [ s ∣ 1 < < x ] dp[s] -> dp[s | 1<<x] dp[s]?>dp[s1<<x]要考虑 x x x s s s 里的字母里的距离 与 x x x s s s 外的字母里的距离

     ∑ k ∈ S c n t [ x ] [ k ] × ( p o s [ x ] ? p o s [ k ] ) + ∑ k ? S c n t [ x ] [ k ] × ( p o s [ k ] ? p o s [ x ] ) \sum_{k\in S}{cnt[x][k] \times (pos[x] - pos[k])} + \sum_{k\notin S}{cnt[x][k] \times (pos[k] - pos[x])} kS?cnt[x][k]×(pos[x]?pos[k])+k/?S?cnt[x][k]×(pos[k]?pos[x])

    每次转移需要计算 S S S 状态下,上式的值,发现可以预处理
    预处理的是每个 S S S 的每个状态位与每个非状态位的距离总和 d [ S ] d[S] d[S]
    那么转移的时候从 d p [ s ] ? > d p [ s ∣ 1 < < x ] , d p [ s ∣ 1 < < x ] = m i n ( . . . , d p [ s ] + d [ s ] ) dp[s] -> dp[s | 1<<x],dp[s | 1<<x] = min(...,dp[s] + d[s]) dp[s]?>dp[s1<<x]dp[s1<<x]=min...dp[s]+d[s]

    这样通过每次转移,都处理了键盘里的字母与非键盘里的字母的距离
    时间复杂度: O ( m 2 × 2 m + m × 2 m ) O(m^2 \times 2^m + m \times 2^m) O(m2×2m+m×2m)


    达到了 4 e 8 4e8 4e8,很明显 c f cf cf 是可以卡过的,但考虑更优的做法的话

    对于 ∑ k ∈ S c n t [ x ] [ k ] × ( p o s [ x ] ? p o s [ k ] ) + ∑ k ? S c n t [ x ] [ k ] × ( p o s [ k ] ? p o s [ x ] ) \sum_{k\in S}{cnt[x][k] \times (pos[x] - pos[k])} + \sum_{k\notin S}{cnt[x][k] \times (pos[k] - pos[x])} kS?cnt[x][k]×(pos[x]?pos[k])+k/?S?cnt[x][k]×(pos[k]?pos[x])
    因为 p o s [ x ] = = S pos[x] == S pos[x]==S 里的 1 1 1 的个数,但 p o s [ k ] pos[k] pos[k] 未知,如果考虑 p o s [ k ] pos[k] pos[k] 则会到达 m 2 × 2 m m^2 \times 2^m m2×2m
    这里我们只考虑插入字母 x x x 时, p o s [ x ] pos[x] pos[x] 对转移的影响

    则上式变为: ∑ k ∈ S c n t [ x ] [ k ] × p o s [ x ] ? ∑ k ? S c n t [ x ] [ k ] × p o s [ x ] \sum_{k\in S}{cnt[x][k] \times pos[x]} - \sum_{k\notin S}{cnt[x][k] \times pos[x]} kS?cnt[x][k]×pos[x]?k/?S?cnt[x][k]×pos[x]

    这样我们在预处理时,设 d [ s ] [ x ] d[s][x] d[s][x] x ? s , ∑ k ∈ s c n t [ x ] [ k ] x \notin s,\sum_{k \in s}{cnt[x][k]} x/?sks?cnt[x][k]
    发现 d [ s ] [ x ] d[s][x] d[s][x] 也可以通过子集的状态转移,设 i ∈ s i \in s is
     d [ s ] [ x ] = d [ s d[s][x] = d[s d[s][x]=d[s ^ i ] [ x ] + c n t [ x ] [ i ] i][x] + cnt[x][i] i][x]+cnt[x][i]
    只需要知道一个 i ∈ s i \in s is,考虑树状数组里的 s & ? s s \& {-s} s&?s 则可以得到 s s s 里的最低位的 1 1 1
    预处理之后, d p dp dp 的转移则要考虑位置的影响
     d p [ n s ] = m i n ( d p [ n s ] , d p [ s ] + p o s [ x ] ? ( d [ s ] [ x ] ? d [ ( ( 1 < < m ) ? 1 ) dp[ns] = min(dp[ns], dp[s] + pos[x] * (d[s][x] - d[((1<<m)-1) dp[ns]=min(dp[ns],dp[s]+pos[x]?(d[s][x]?d[((1<<m)?1) ^ n s ] [ x ] ) ) ns][x])) ns][x]))

    那么问题来了,为什么只考虑 p o s [ x ] pos[x] pos[x] 对转移的影响,而不考虑 p o s [ k ] pos[k] pos[k] 的影响?
    观察 ∑ k ∈ S c n t [ x ] [ k ] × ( p o s [ x ] ? p o s [ k ] ) ? ∑ k ? S c n t [ x ] [ k ] × ( p o s [ k ] ? p o s [ x ] ) \sum_{k\in S}{cnt[x][k] \times (pos[x] - pos[k])} - \sum_{k\notin S}{cnt[x][k] \times (pos[k] - pos[x])} kS?cnt[x][k]×(pos[x]?pos[k])?k/?S?cnt[x][k]×(pos[k]?pos[x])
    发现对于 p o s [ k ] pos[k] pos[k],在不断的转移计算过程中,最终会全部抵消掉,总和为 0 0 0
    也就是 p o s [ k ] pos[k] pos[k] 对最终答案是没用影响的
    那么现在的状压 d p dp dp 所表示的就不是原本的状压了,中间的过程状态都是错的
    但最终的结果是正确的的(QwQ ~~~)

    时间复杂度: O ( m × 2 m ) O( m \times 2^m) O(m×2m)

核心:预处理状态转移,子集优化dp

#include<bits/stdc++.h>
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 5;
int n, m, cnt[25][25];
int d[1<<21][25], dp[1<<21];
char s[maxn];int main() {scanf("%d%d%s", &n, &m, s+1);for(int i=1; i<n; i++){cnt[s[i]-'a'][s[i+1]-'a']++;cnt[s[i+1]-'a'][s[i]-'a']++;}for(int s=1; s<1<<m; s++)for(int i=0; i<m; i++)d[s][i] = d[s & (s - 1)][i] + cnt[i][__builtin_ffs(s) - 1];memset(dp, 0x3f, sizeof(dp));dp[0] = 0;for(int s=0; s<1<<m; s++){for(int i=0; i<m; i++){if(1 << i & s) continue;int ns = 1 << i | s;int pos = __builtin_popcount(s);dp[ns] = min(dp[ns], dp[s] + pos * (d[s][i] - d[((1<<m)-1) ^ ns][i]));}}printf("%d\n", dp[(1<<m)-1]);
}
  相关解决方案