Educational Codeforces Round 74E 状态压缩dp
https://codeforces.com/problemset/problem/1238/E
题意:
一个字符串,你来安排一个字符排列,计算使得(sum_{i=1}^{n}|pos_{s_{i-1}}-pos_{s_{i}}|)最小。(例子里面很清楚
思路:
状态压缩dp,dp[111111]表示都放进去的最小值就好了,但是这个最小是什么最小就很微妙了,比如说如果我维护目前开销最小肯定是不行的,因为状态放进去的顺序是影响答案的,我们也没法转移,也就是无法取消后效性。这里的最小考虑最总贡献最小,使得单次转移只和当前前面有几个有关,我们如何做到?实际上观察表达式,应该是每对要求的字母后面的位置减前面的位置的和,也就是(cnt_{x,y}*(pos_{x}-pos_{y}))这里我们为了达到无后效性,算每个单词的“贡献”。把每个单词的加入分成两个部分,一部分是之前有多少个cnt产生了什么影响,一个是之后有多少个cnt产生了什么影响。说的混乱,可以看作是利用差分达到计算对最终答案的贡献。枚举最后插入的字符,转移方程为(dp[x]=min(dp[x],popcount(new\_x)*(sum_{jin new\_x}cnt_{i,j}-sum_{j
otin new\_x}cnt_{i,j})))具体看代码就好了。
代码:
#include <bits/stdc++.h>
using namespace std;
#define X first
#define Y second
#define PB push_back
#define LL long long
#define pii pair<int,int>
#define MEM(x,y) memset(x,y,sizeof(x))
#define bug(x) cout<<"debug "#x" is "<<x<<endl;
#define FIO ios::sync_with_stdio(false);
#define ALL(x) x.begin(),x.end()
#define LOG 20
const int inf =0x3f3f3f3f;
const int maxn =(1<<20)+7;
int dp[maxn];
map<int,int> h;
int sum[maxn][22];
int cnt[22][22];
int n,m;
string s;
int lower_bit(int x){return x&-x;};
int get_sum(int x,int i){
if(sum[x][i]!=-1)return sum[x][i];
if(x==0) return sum[x][i]=0;
sum[x][i]=get_sum(x&(x-1),i)+cnt[h[lower_bit(x)]][i];
return sum[x][i];
}
int DP(int x){
if(dp[x]!=-1) return dp[x];
if(x==0)return dp[x]=0;
int t=x;
dp[x]=inf;
for(int i=0;i<m;i++){
if(x&(1<<i)){
int p=x^(1<<i);
int q=x^((1<<m)-1);
dp[x]=min(dp[x],DP(p)+__builtin_popcount(p)*(get_sum(p,i)-get_sum(q,i)));
}
}
return dp[x];
}
int main(){
FIO;
cin>>n>>m>>s;
MEM(dp,-1);MEM(sum,-1);
for(int i=0;i<m;i++) h[1<<i]=i;
for(int i=1;i<n;i++){
++cnt[s[i] - 'a'][s[i - 1] - 'a'];
++cnt[s[i - 1] - 'a'][s[i] - 'a'];
}
cout<<DP((1<<m)-1)<<endl;
return 0;
}