计算贡献;压缩空间优化时间
传送门:$>here<$
题意
给出两个字符串$a$,$b$,将他们穿插起来(相对位置不变),要求最小化$\sum L(c)$,其中$L(c)$的定义时在穿插完的字符串中字符$c$的最大位置与最小位置的差。
数据范围:$n \leq 5000$
Solution
问题的转化
根据$LCS$的模型容易想到$dp_{i,j}$表示$a$的前$i$个与$b$的前$j$个穿插后的最小$L$。转移即接$i$或接$b$.
如何转移呢?如果要利用这样的状态进行转移,肯定需要知道每种字符出现的最早位置。这样的话状态就变得很大了……
既然用直接的方法无法进行转移,那么必须改变计算方法!将dp(i,j)的意义改为将a的前i个与b的前j个合并后对最终答案的最优贡献。也就是说,不要将dp(i,j)作为子问题去考虑,而是考虑这一部分对整体的贡献。
将题目要求的某种字符的最大位置与最小位置的差看做一条线段,这条线段从这个字符首次出现开始延伸,直到最后一个这种字符出现。那么题目要求最小化的就是26根线段的总长度。那么dp(i,j)就是表示填完a的前i个与b的前j个以后线段的最小总长。
利用DP来解决
要计算最小总长(假设现在选择将a[i]放在最后),那么就考虑将a[i]填入后总长增加多少?很显然,对于那些还没有出现完的字符,它们对应的线段长度就要+1。什么意思?对于a[i]前面的每种字符,若还有可能在a[i]后方出现,那么对应的线段长度需要延伸。如果某种字符在a[i]之后再也不会出现了,那就不用延伸了。因此,填完a[i]后延伸的总长度就是还没有出现完的字符个数。
值得注意的是,这里的dp(i,j)既然不是子问题,那是如何保证DP的性质的呢?结合深搜来理解一下,假设最终字符的最后几位怎么填已经知道,那么前面怎么填最优?由于之前出现了哪些字符是确定的,因此最后几位在填的时候延伸的总长度是确定的,那么也就是最小化前面的总长度,那么也就是dp(i,j)了。
透过题解看本质
问题的转化
当发现当前问题极难转移时,不妨换一种计算思路。既然子问题无法考虑,那就整体考虑。
形象生动
把位置之差理解为线段很直观便于思考。避免去空想那些太抽象的东西 。
部分解并不独立有意义
不仅仅是因为这道题部分考虑比较难所以选择整体考虑,而是因为这道题的很多部分解对于转移是没有意义的。
my code
值得注意的是如果用二维DP会炸。幸好可以直接滚动数组变成1维。
还发现优化空间能够帮助优化时间。
/*By DennyQi 2019*/ #include <cstdio> #include <queue> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; const int MAXN = 10010; const int MAXM = 20010; const int INF = 0x3f3f3f3f; inline int Max(const int a, const int b){ return (a > b) ? a : b; } inline int Min(const int a, const int b){ return (a < b) ? a : b; } inline int read(){int x = 0; int w = 1; register char c = getchar();for(; c ^ '-' && (c < '0' || c > '9'); c = getchar());if(c == '-') w = -1, c = getchar();for(; c >= '0' && c <= '9'; c = getchar()) x = (x<<3) + (x<<1) + c - '0'; return x * w; } int T,dp[5010],cnt[5010],lsta[30],lstb[30],fsta[30],fstb[30],la,lb; char a[5010],b[5010]; inline void Init_cnt(){memset(lsta,0,sizeof(lsta));memset(lstb,0,sizeof(lstb));memset(fsta,0x3f,sizeof(fsta));memset(fstb,0x3f,sizeof(fstb));for(int i = 1; i <= la; ++i){lsta[a[i]-'A'] = max(lsta[a[i]-'A'],i);fsta[a[i]-'A'] = min(fsta[a[i]-'A'],i);}for(int i = 1; i <= lb; ++i){lstb[b[i]-'A'] = max(lstb[b[i]-'A'],i);fstb[b[i]-'A'] = min(fstb[b[i]-'A'],i);} } inline void DP(){memset(dp,INF,sizeof(dp));memset(cnt,0,sizeof(cnt));char cur;dp[0] = 0;for(int i = 0; i <= la; ++i){for(int j = 0; j <= lb; ++j){if(i==0 && j==0) continue;if(i == 0){ cnt[j] = cnt[j-1];cur = b[j]-'A';if(j == fstb[cur]) ++cnt[j];if(j == lstb[cur] && lsta[cur] == 0) --cnt[j];}else{cur = a[i]-'A';if(j >= lstb[cur] && i >= lsta[cur]) --cnt[j];if(j < fstb[cur] && i == fsta[cur]) ++cnt[j];}dp[j] = min((j==0)?INF:dp[j-1], (i==0)?INF:dp[j])+cnt[j];}} } int main(){scanf("%d",&T);while(T--){scanf("%s%s",a+1,b+1);la = strlen(a+1), lb = strlen(b+1);Init_cnt();DP();printf("%d\n",dp[lb]);}return 0; }