Color Length UVA - 1625

点击打开链接

UVa1625

题意:输入两个长度分别为n和m(n,m<=5000)的颜色序列,要求按顺序合并成一个序列,也就是每次从n或者m的开头取一个颜色,将这个颜色从原序列去掉并放入新序列的尾端。
对于每个颜色C来说,L(C)表示合并后的序列中C最后出现的位置与最前出现的位置之差。现在要使得L(C)的总和最小。

要得到状态(i,j)(表示n移走了i个,m移走了j个),可以是n移走i-1个,m移走j个后n再移走一个,或者n移走i个,m移走j-1个后m再移走一个。因此,很显然ans[i][j]可以由ans[i-1][j]或ans[i][j-1]转移得到。(ans[i][j]表示在(i,j)的序列中已知的L(C)值之和的最小值,这里说“已知的”是因为有一些字母的最后一个还没有出现在此序列中,仍然在n或m中,这种情况下则统计从新序列中那个字母所在位置到序列尾部已经产生的L()值)
如何转移状态?
例如此时GBBY、YRRGB的(1,3)状态的序列为YRRG,那么要得到由(1,3)转移到的(1,4)状态的答案(不一定是(1,4)的最终答案,可能还要考虑(0,4)转移到的答案),就是要把G插入到YRRG的最后。
由于两个序列中所有R在到达状态(1,3)之时已经放入了合并后的序列中,因此R对(1,4)的答案不可能再产生贡献了,也就是说从(1,3)转移到(1,4)的过程中,L(R)已经确定,不对L(C)的总和产生影响。
由于两个序列中,有一部分Y和G在到达(1,3)之时已经放入了合并后的序列中,有一部分却没有,因此如果在(1,3)的序列末尾插入一个颜色,那么Y和G已知的L(C)会各加上1,答案也就加上2。
由于两个序列中,任何的B都尚未在到达(1,3)之时已经放入合并后的序列,因此放入新元素时B不会对答案产生贡献。
可以看出的是,如果预处理出在(i,j)状态向后转移的时候有多少个颜色是处于“有一部分该颜色在到达(i,j)之时已经放入了合并后的序列中,有一部分却没有”的状态的(因为只有该状态会产生贡献),将大大方便计算。
设这个预处理后的数组为c,那么,ans[i][j]=min(ans[i-1][j]+c[i-1][j],ans[i][j-1]+c[i][j-1])。而在计算ans[i][j]的时候,只用到ans[i-1][j]和ans[i][j-1],因此可以去掉一维,转移方程变为ans[j]=min(ans[j]+c[i-1][j],ans[j-1]+c[i][j-1])。
按i在外,j在内,i、j都是从小到大循环即可。

然而由于数据范围太大了,c也需要压缩为一维。可以压缩为c[j],在每次计算完ans[j]时更新c[j]。这样在计算ans[j]的时候(第i次循环,也就是ans[j]其实是ans[i][j]),取c[j]相当于c[i-1][j],取c[j-1]相当于c[i][j-1]。
那么如何计算c呢?
同样是递推。先考虑没有压缩时的c。c[i][j]=c[i][j-1]+m中第j个是否是最先出现的(是1/否0)-m中第j个是否是最后出现的(是1/否0)。c[i][j]=c[i-1][j]+n中第j个是否是新出现的(是1/否0)-n中第j个是否是新结束的(是1/否0)。因此需要预处理出每个字母在n、m中首次出现与最后出现的位置。

举例:
GBBY YRRGB
b1['G']=e1['G']=1
b1['B']=2
e1['B']=3
b1['Y']=e1['Y']=4
b2['Y']=e2['Y']=1
b2['R']=2
e2['R']=3
b2['G']=e2['G']=4
b2['B']=e2['B']=5
(b1,e1,b2,e2分别表示n中字符最前、最后出现的位置,m中字符最前、最后出现的位置)

如何求出b1,e1,b2,e2呢?

很简单,从头开始扫n和m,得到b1和b2,从尾开始扫n和m,得到e1和e2,具体见程序

设m中第j个字母为ch,则如果b2[ch]==j&&b1[ch]>i,其在(i,j)状态下为第一次出现
设m中第j个字母为ch,则如果e2[ch]==j&&e1[ch]<=i,其在(i,j)状态及由(i,j)能到的状态下为最后一次出现
对于n也是类似的。(具体见程序)

然后,直接就可以压缩掉第二维了,甚至多余的处理都不用(好神奇)

最后,还有一些小细节需要处理,例如字符串的开头是0而不是1,但前面推理时对字符串里面字符的编号都是从1开始的。还有b1,e1,b2,e2的初值需要处理。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int b1[200],e1[200],b2[200],e2[200];
int n,m,cse;
//int ans[200][200];
//int c[200][200];
int ans[5010];
int c[5010];
char s1[5010],s2[5010];
int main()
{
	int i,j;
	scanf("%d",&cse);
	while(cse--)
	{
		scanf("%s",s1);
		scanf("%s",s2);
		n=strlen(s1);
		m=strlen(s2);
		memset(b1,0x3f,sizeof(b1));
		memset(e1,-1,sizeof(e1));
		memset(b2,0x3f,sizeof(b2));
		memset(e2,-1,sizeof(e2));
		for(i=0;i<n;i++)
			if(b1[s1[i]]==0x3f3f3f3f)
				b1[s1[i]]=i+1;
		for(i=n-1;i>=0;i--)
			if(e1[s1[i]]==-1)
				e1[s1[i]]=i+1;
		for(i=0;i<m;i++)
			if(b2[s2[i]]==0x3f3f3f3f)
				b2[s2[i]]=i+1;
		for(i=m-1;i>=0;i--)
			if(e2[s2[i]]==-1)
				e2[s2[i]]=i+1;
		memset(c,0,sizeof(c));
		//memset(ans,0,sizeof(ans));
		for(i=0;i<=n;i++)
			for(j=0;j<=m;j++)
			{
				if(i&&j)
					ans[j]=min(ans[j]+c[j],ans[j-1]+c[j-1]);
				else if(j==0&&i)
					ans[j]=ans[j]+c[j];
				else if(i==0&&j)
					ans[j]=ans[j-1]+c[j-1];
				else	ans[j]=0;
				if(j)
				{
					c[j]=c[j-1];
					if(b2[s2[j-1]]==j&&b1[s2[j-1]]>i)
						c[j]++;
					if(e2[s2[j-1]]==j&&e1[s2[j-1]]<=i)
						c[j]--;
				}
				else if(i)
				{
					if(b1[s1[i-1]]==i&&b2[s1[i-1]]>j)
						c[j]++;
					if(e1[s1[i-1]]==i&&e2[s1[i-1]]<=j)
						c[j]--;
				}
			}
		
//		
//		for(i=0;i<=n;i++)
//			for(j=0;j<=m;j++)
//				if(j)
//				{
//					c[i][j]=c[i][j-1];
//					if(b2[s2[j-1]]==j&&b1[s2[j-1]]>i)
//						c[i][j]++;
//					if(e2[s2[j-1]]==j&&e1[s2[j-1]]<=i)
//						c[i][j]--;
//				}
//				else if(i)
//				{
//					c[i][j]=c[i-1][j];
//					if(b1[s1[i-1]]==i&&b2[s1[i-1]]>j)
//						c[i][j]++;
//					if(e1[s1[i-1]]==i&&e2[s1[i-1]]<=j)
//						c[i][j]--;
//				}
//		for(i=0;i<=n;i++)
//			for(j=0;j<=m;j++)
//			{
//				ans[i][j]=0x3f3f3f3f;
//				if(i)
//					ans[i][j]=min(ans[i][j],ans[i-1][j]+c[i-1][j]);
//				if(j)
//					ans[i][j]=min(ans[i][j],ans[i][j-1]+c[i][j-1]);
//				if(i==0&&j==0)
//					ans[i][j]=0;
//			}
		printf("%d\n",ans[m]);
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值