Educational Codeforces Round 74 (Rated for Div. 2)E(状压DP,降低一个m复杂度做法含有集合思维)

#define HAVE_STRUCT_TIMESPEC
#include<bits/stdc++.h>
using namespace std;
char s[100005];
int pos[(1<<20)+5];//pos[i]表示i=1<<j时的j
int cnt[35][35];//cnt[i][j]表示s[i]=i,s[i+1]=j的移动数量
int g[25][(1<<20)+5];//g[i][S],i不属于S表示s[i]=i,s[i+1]∈S的移动数量
int h[(1<<20)+5];//h[S]表示所有s[i]∈S,s[i+1]不属于S的移动数量
int dp[(1<<20)+5];
int main(){
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int n,m;
cin>>n>>m;
cin>>s+1;
for(int i=1;i<n;++i){
int gg=s[i]-'a';
int tt=s[i+1]-'a';
++cnt[gg][tt];
++cnt[tt][gg];
}
for(int i=0;i<m;++i)
pos[1<<i]=i;
for(int i=0;i<m;++i)
for(int j=1;j<(1<<m)-1;++j)
if(!((j>>i)&1))
g[i][j]=g[i][j-(j&-j)]+cnt[i][pos[j&-j]];//i和j不在同一集合内,g[i][j]表示在集合j中加入字母i的话会增加多少次移动
for(int i=1;i<(1<<m)-1;++i)
for(int j=0;j<m;++j)
if((i>>j)&1)
h[i]+=g[j][i^((1<<m)-1)];//j和i在同一集合内,枚举i内的所有元素j,求和得到集合i的补集中加依次入集合i中所有元素会增加多少次移动
for(int i=1;i<(1<<m);++i)
dp[i]=1e9+7;
dp[0]=0;
for(int i=0;i<(1<<m)-1;++i)//i上为1的位表明该字母在键盘上的位置已经确定,键盘上从左到右的顺序取决于dp过程中先后被加入进dp的h,随着更新最小值,从左到右顺序也被暗中替换为更小值的顺序(最左边字母进入时因为只有一个字母所以不会产生移动,它的贡献是最初的dp[i],i中只有一位1,dp[i]=0)
for(int j=0;j<m;++j)
if(!((i>>j)&1))
dp[i^(1<<j)]=min(dp[i^(1<<j)],h[i]+dp[i]);//求dp[i^(1<<j)]相当于在dp[i]已有的键盘顺序上最右端加入字母j,此时所有已经在键盘上的字母和剩下所有还不在键盘上的字母相邻的话移动距离都会增加一格,所以要加上h[i],即i上所有为1的位j的g[j][k]之和,k是i的补集也就是还不在键盘上的那些字母
//其实如果不为了复杂度降低一个m的话,可能更容易理解一些,这道题的关键在于每次向已经排列好的键盘右端加入一个新字母,此时依次将所有已经在键盘上的字母和与其相邻的所有还未在键盘上的字母个数相加,尽可能复杂度低的计算出这些集合与集合之间的相邻个数。
cout<<dp[(1<<m)-1];
return 0;
}

保持热爱 不懈努力 不试试看怎么知道会失败呢(划掉) 世上无难事 只要肯放弃(划掉)
原文地址:https://www.cnblogs.com/ldudxy/p/11651621.html