UVA1625 - Color Length (DP)

题目链接 UVA1625 (题目看不到请看PDF)

 【题意】

 

【分析】

 

我是用dp[i][j]表示第一个序列移除i个元素,第二个序列移除j个元素时候的最小的权值。

然后对于怎样在o(1)时间内计算出移除当前元素的费用,想了两天也没想出来,最后还是看了别人的代码才明白的。唉,DP弱啊。

其实只要用两个数组f[]来记录每个元素在每个序列是否已经出现即第一次出现后一直标记为1,其他标记为0;用g[][]来记录当前元素在当前序列是否已经结束,即结束后的位置都标记为0,其他标记为1;也就是用f[][]记录每个元素在当前序列的开始位置,g[][]记录每个元素在当前序列的结束位置;两个数组不能用一个来表示,怎样没法判断,下面会说;

这样记录的话直接可以在O(1)时间判断当前去掉的元素是不是开始和结束位置。例如:当前已经在第一个序列移除了i个元素,第二个序列已经移除了j个元素,如果现在要移除第一个序列的第i+1个元素,那么只要判断i+1个元素的f[0][i]是否为0(如果为0的话那么当前元素在第一个序列第i+1个位置肯定是首次出现,因为已经把后面所有f[][]全部标记为1了,而当前既然已经是这个元素,那么当前f[0][i+1]必定是1,再判断i+1的元素在第二个序列是否已经出现f[1][j];如果f[0][i]和f[1][j]都为0说明这个元素没有没删除过),同样可以通过i+1后一个位置g[][]是不是0来判断i+1的元素是不是已经结束了。

有了每个元素在每个序列的开始和结束位置,以及当前状态在去掉某个元素是否是第一次删除以及最后一次删除;

每个状态只由两个状态dp[i-1][j]或者dp[i][j-1]转移过来,当选进来的元素是第一次出现的时候,权值减去当前坐标(i+j),当这种元素在剩下未选的元素中不会再出现时,权值加上(i+j)

【AC代码】76ms

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define MAXN 5005
struct NODE{//f[]记录每个字母是否已经出现,g[]记录每个字母是否已经结束
	bool f[2][MAXN], g[2][MAXN];
}s[30];
char a[2][MAXN];
int dp[MAXN][MAXN], len[2];

void find()//寻找每个字母在两个序列首次出现和结束的位置
{
	for (int i = 0; i < 26; i++)
	{
		for (int k = 0; k < 2; k++)
		{
			int st = -1, ed = -1;
			for (int j = 0; j < len[k]; j++)
			{
				if (i + 'A' == a[k][j])
				{
					st = j;
					break;
				}
			}
			for (int j = len[k] - 1; j >= 0; j--)
			{
				if (i + 'A' == a[k][j])
				{
					ed = j;
					break;
				}
			}
			if (-1 != st)
			{
				for (int j = st + 1; j <= len[k]; j++)
					s[i].f[k][j] = 1;
				for (int j = 1; j <= ed + 1; j++)
					s[i].g[k][j] = 1;
			}
		}
	}
}
int main()
{
#ifdef SHY
	freopen("e:\\1.txt", "r", stdin);
#endif
	int t;
	scanf("%d%*c", &t);
	while (t--)
	{
		gets(a[0]), len[0] = strlen(a[0]);
		gets(a[1]), len[1] = strlen(a[1]);
		memset(s, 0, sizeof(s));
		memset(dp[0], 0x3f, sizeof(dp[0]));
		dp[0][0] = 0;
		find();
		for (int i = 0; i <= len[0]; i++)
		{
			memset(dp[i + 1], 0x3f, sizeof(dp[i + 1]));//直接对dp[][]一起赋值会超时
			for (int j = 0; j <= len[1]; j++)
			{
				if (i < len[0])//在第一个序列移除i+1的时候
				{
					int t = dp[i][j], p = a[0][i] - 'A';
					if (!s[p].f[0][i] && !s[p].f[1][j]) t -= i + j;//当前字母p是不是第一次被移除
					if (!s[p].g[0][i + 2] && !s[p].g[1][j + 1]) t += i + j;//当前字母是不是最后一次被移除
					dp[i+1][j] = min(dp[i+1][j], t);
				}
				if (j < len[1])//在第二个序列移除j+1的时候
				{
					int t = dp[i][j], p = a[1][j] - 'A';
					if (!s[p].f[0][i] && !s[p].f[1][j]) t -= i + j;
					if (!s[p].g[0][i + 1] && !s[p].g[1][j + 2]) t += i + j;
					dp[i][j + 1] = min(dp[i][j + 1], t);
				}
			}
		}
		printf("%d\n", dp[len[0]][len[1]]);
	}
	return 0;
}


 

【状态压缩+滚动数组后AC代码】29ms

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define MAXN 5005
#define to(x) (1<<(x))
char a[MAXN], b[MAXN];
int dp[2][MAXN], lena, lenb, f[2][MAXN], g[2][MAXN];
//f[]记录每个字母是否已经出现,g[]记录每个字母是否已经结束

int main()
{
#ifdef SHY
	freopen("e:\\1.txt", "r", stdin);
#endif
	int t;
	scanf("%d%*c", &t);
	while (t--)
	{
		gets(a), lena = strlen(a);
		gets(b), lenb = strlen(b);
		memset(dp, 0x3f, sizeof(dp));
		g[0][lena + 1] = g[1][lenb + 1] = dp[0][0] = 0;
		for (int i = 1; i <= lena; i++)
		{
			f[0][i] = f[0][i - 1] | to(a[i-1]-'A');
			g[0][lena - i + 1] = g[0][lena - i + 2] | to(a[lena-i]-'A');
		}
		for (int i = 1; i <= lenb; i++)
		{
			f[1][i] = f[1][i - 1] | to(b[i-1]-'A');
			g[1][lenb - i + 1] = g[1][lenb - i + 2] | to(b[lenb-i]-'A');
		}
		int now = 1, next = 0;
		for (int i = 0; i <= lena; i++)
		{
			now = !now, next = !next;
			memset(dp[next],0x3f,sizeof(dp[next]));
			for (int j = 0; j <= lenb; j++)
			{
				if (i < lena)
				{
					int t = dp[now][j];
					if (~(f[0][i] | f[1][j])&to(a[i] - 'A')) t -= i + j;
					if (~(g[0][i + 2] | g[1][j + 1])&to(a[i] - 'A')) t += i + j;
					dp[next][j] = min(dp[next][j], t);
				}
				if (j < lenb)
				{
					int t = dp[now][j];
					if (~(f[0][i] | f[1][j])&to(b[j] - 'A')) t -= i + j;
					if (~(g[0][i + 1] | g[1][j + 2])&to(b[j] - 'A')) t += i + j;
					dp[now][j + 1] = min(dp[now][j+1], t);
				}
			}
		}
		printf("%d\n", dp[now][lenb]);
	}
	return 0;
}



 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值