这道题过得很悬啊,5000*5000*25的数据3s竟然过了……本来加的优化全部都可以删了,在我看来这是一个伪 O(mn) O ( m n ) ~
这里好不容易找到了PDF版的
附两个传送门(UVA有点慢):
vjudge
UVA
输入两个长度为n,m(n,m<=5000)的颜色序列a串和b串,要求按顺序合并成同一个序列,即每次可以吧一个序列开头的颜色放到新序列的尾部.现在对于每个颜色来说,其跨度L(c)等于最大位置和最小位置之差,现在请你找出一种合并方式使得所有L(c)总和最小。
贴心的我把输入输出打一遍好让你们测试~:
in i n
2
AAABBCY
ABBBCDEEY
GBYY
YRRGB
out o u t
10
12
在这里我们很容易就写出了DP状态的定义:
f[i][j]:a串移走前i个,b串移走前j个的费用 f [ i ] [ j ] : a 串 移 走 前 i 个 , b 串 移 走 前 j 个 的 费 用
注意,这里的费用说的极为模糊不易于我们写代码,因为在这道题中状态转移较为复杂,我们不易于记录状态当一颜色第一次出现在合并序列时,我们不知道它什么时候结束,同样对于一颜色最后一次出现在序列里时,我们并不知道它什么时候开始的。
所以我们并不能一个一个颜色的转移.
那怎么做呢?我们就直接对于一一开始但未结束的颜色一次一次地累加,那这里的费用算到最后就是答案.就比较清晰了
状态转移方程的一部分是:
f[i][j]=min(f[i−1][j],f[i][j−1]) f [ i ] [ j ] = m i n ( f [ i − 1 ] [ j ] , f [ i ] [ j − 1 ] )
注意只要有越界就不考虑这种情况
意思是我们现在合并的序列长度为 i+j i + j ,我们要从状态 i+j−1 i + j − 1 后面填一个a串的颜色或b串的颜色转移到 i+j i + j 同时必须满足两状态中必须选a串的长度相等或b串的长度相等,因为这是DP
我们一开始就要预处理出对于一个颜色在a串和b串分别第一次出现,最后一次出现的位置(这里记为fa,fb,la,lb)
我们在枚举i,j中在增加一个循环,枚举颜色k,只要对于当前位置已开始未结束,当前状态就加1
我们把加1单独用cnt来存,那么状态转移方程式就是:
f[i][j]=min(f[i−1][j],f[i][j−1])+cnt f [ i ] [ j ] = m i n ( f [ i − 1 ] [ j ] , f [ i ] [ j − 1 ] ) + c n t
注意枚举k判断时间开始和结束这样判断比较好一些:
(fa[k]<=i||fb[k]<=j)&&(i
对于fa,fb,la,lb来说初值很重要,fa,fb初值全为INF,la,lb初值全为0,因为这样说明所有颜色都不能选.而预处理完后没有改变的就在DP中不合法了(也就是不会选到)
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define LL long long
using namespace std;
#define INF 0x3f3f3f3f
#define MAXN 5000
int f[MAXN+5][MAXN+5];
char a[MAXN+5],b[MAXN+5];
int fa[30],fb[30],la[30],lb[30];
int main(){//f[i][j]:a串移除i个,b串移除j个中
int T;scanf("%d",&T);//有多少颜色出现的计算最小值
while(T--){
scanf("%s %s",a+1,b+1);
memset(la,0,sizeof(la));//初始化
memset(lb,0,sizeof(lb));
memset(fa,0x3f,sizeof(fa));
memset(fb,0x3f,sizeof(fb));
int n=strlen(a+1),m=strlen(b+1);
for(int i=1;i<=n;i++){//预处理
if(fa[a[i]-'A']==INF) fa[a[i]-'A']=i;
la[a[i]-'A']=i;
}
for(int i=1;i<=m;i++){
if(fb[b[i]-'A']==INF) fb[b[i]-'A']=i;
lb[b[i]-'A']=i;
}
for(int i=0;i<=n;i++){//DP
for(int j=0;j<=m;j++){
int cnt=0;//统计颜色对于当前合并位置
for(int k=0;k<=25;k++)//已开始但未结束的种数
if((fa[k]<=i||fb[k]<=j)&&(iif(i&&j) f[i][j]=min(f[i-1][j],f[i][j-1])+cnt;
else if(i) f[i][j]=f[i-1][j]+cnt;//越界处理
else if(j) f[i][j]=f[i][j-1]+cnt;
}
}
printf("%d\n",f[n][m]);
}
return 0;
}
这个数据规模我也不知道我是怎么过的..
.但是我们可以优化,明显可以发现,cnt可以维护而不是每次重复计算,我们对于每个颜色可以建立一个整型vis来表示二进制中上面判断颜色已开始但未结束的四个信息是否从成立
cnt直接定义在Dp外,只要每次在外层循环更新vis时发现vis%4和vis/4均有值cnt就+1,否则就-1
而进入内层循环时要用一个tmp把当前vis复制下来用tmp更新cnt,否则vis在下一次就要耗时恢复了.
这样就不用枚举内层的k了时间复杂度就真的变为 O(nm) O ( n m )