题意
输入两个长度分别为n和m的颜色序列,要求按顺序合并成同一个序列,即每次把一个序列开头颜色放到新队列的尾部。对于每种颜色,函数L(c)表示新队列中该种颜色最后一次出现的位置坐标减第一次出现的位置坐标。现求所有L(c)之和的最小值。
题解
在解决本题的过程中,该题中的一些想法得到了实践,并且取得较好的效果。但在写状态转移方程的过程中遇到困难——没有找到指标函数。
自己理解的指标函数是:在状态转移过程中,具体选择哪个决策需要根据每种决策的具体优劣值,或是说每种决策的指标函数值。有时,指标函数就是前一状态的dp值;有时则还需要加上一个转移代价。转移代价就是从上一状态转移到当前转态的代价,它是和定义的dp统一的。
同时,此题还有一个陷阱。如果将dp(i,j)定义为两个序列分别处理到i和j位置处的答案数,而答案数是L(c)之和,L(c)的计算需要具体的位置坐标吗,这就导致我们很难求出该状态下的指标函数。因为我们只知道上一状态的dp值,而不知道具体的排列状况。解决这个问题有两种思路,一是记录下每一状态的排列状况(通过增加转态或另外保存),但这种方法(针对本题)会使转态十分复杂并且消耗大量空间。而另一种方法是改变dp的定义。
在此之前,先提出自己的猜想。dp问题中所求答案有两种形式:一是转移代价与位置无关,二是转移代价与位置有关。这里的位置是指过程中的某阶段。对于与位置无关的dp,其指标函数往往简单,转移代价只需根据当前状态便可算出,例如+1或是+w[i]等。而与位置有关的dp又有两种情况:一种是位置信息可以保存,例如 Partitioning by Palindromes。可以在可接受的时间复杂度内计算出转移代价,或是通过预处理出所有可能的转移代价。另一种则如本题,转移代价与位置有关且位置信息无法保存。对于这样的题,我们不直接将dp定义为答案数,而是定义为对最终答案的贡献。这样,转移代价也就不着眼于子问题,而是某一步骤。
回到本题,当我们从某一状态转移到另一转态时,我们仅能知道的是已经有哪些颜色合并进了新序列。转移时我们是将一个颜色加入序列,在这个过程中,如果新序列中的某种颜色已经全部用掉,那么新加的颜色对其L(c)值无影响。而如果有已经加入新序列而未结束的颜色来说,会使其L(c)值加一,而这个L(c)值是全局意义上的。
综上,代码如下:
AC代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
using namespace std;
const int maxn = 5000+5;
const int INF = 0x3f3f3f3f;
int dp[maxn][maxn];
char a[maxn],b[maxn];
int color[2][27][2];
bool vc[27];
int w(int i, int j){
i--;j--;
int cnt = 0;
for(int k = 0; k<26; k++)if(vc[k]){
if(color[0][k][0] != -1 && color[1][k][0]!=-1){
if(!((i<color[0][k][0]&&j<color[1][k][0])||
(i>=color[0][k][1]&&j>=color[1][k][1])))cnt++;
}
else if(color[0][k][0] != -1){
if(color[0][k][0]<=i&&i<color[0][k][1]) cnt++;
}
else{
if(color[1][k][0]<=j&&j<color[1][k][1]) cnt++;
}
}
return cnt;
}
int main(){
int T;
scanf("%d", &T);
while(T--){
scanf("%s", a);
scanf("%s", b);
int n = strlen(a), m = strlen(b);
memset(vc, false, sizeof(vc));
for(int i = 0; i<2; i++)
for(int j = 0; j<27;j++)
for(int k = 0; k<2; k++){
color[i][j][k] = -1;
}
bool cb[26];
memset(cb, false, sizeof(cb));
for(int i = 0; i<n; i++)if(!cb[a[i]-'A']){
vc[a[i]-'A'] = cb[a[i]-'A'] = true;
int j = n-1;
while(a[j] != a[i]) j--;
color[0][a[i]-'A'][0] = i;
color[0][a[i]-'A'][1] = j;
}
memset(cb, false, sizeof(cb));
for(int i = 0; i<m; i++)if(!cb[b[i]-'A']){
vc[b[i]-'A'] = cb[b[i]-'A'] = true;
int j = m-1;
while(b[j] != b[i]) j--;
color[1][b[i]-'A'][0] = i;
color[1][b[i]-'A'][1] = j;
}
for(int i = 1; i<=n; i++)dp[i][0] = dp[i-1][0] + w(i-1,0);
for(int i = 1; i<=m; i++)dp[0][i] = dp[0][i-1] + w(0, i-1);
for(int i = 1; i<=n; i++){
for(int j = 1; j<=m; j++){
dp[i][j] = min(dp[i-1][j]+w(i-1, j), dp[i][j-1]+w(i, j-1));
}
}
printf("%d\n",dp[n][m]);
}
return 0;
}