Cities (2020ICPC-昆明)(区间dp)

10 篇文章 0 订阅

第 45 届国际大学生程序设计竞赛(ICPC)亚洲区域赛(昆明)
参考题解

题意

给出一个长度为n的序列,序列中每个元素均小于等于n,每次可以将序列中连续且相等的区间变为其他的值(更改后的当然也必须小于等于n)。
求:最小的操作次数,使得长度为n的序列所有的元素均相同。

Input

2
8
4 3 1 2 1 1 3 3
5
1 2 3 2 1

Output

2
8
4 3 1 2 1 1 3 3
5
1 2 3 2 1

思路

区间dp。

  1. 假设原序列为a[n]
  2. 首先将序列中相邻且相同的元素合并,也即删掉相邻的相同元素(剩下的元素为m, 然后令n = m)
  3. 对于一段区间[ l, r], 用最少的次数变为相同的颜色,这个区间的颜色最终一定可以变为a[l]或a[r]
  4. 考虑如何减少操作:显然,如果n个元素互不相同,则最少操作次数为 n - 1;否则,假设a[l] = a[r],那么将a[l + 1, r - 1] 涂为 a[l] 或 a[r],可减少一次操作。
  5. f[ l ][ r ]表示将区间[ l, r ]变为相同的颜色的最小操作次数
  6. 设pre[ a[ i ] ] 表示 a[i] 在 i 之前最近一次出现的位置(例如:2 3 1 4 3 5, a[5] = 3, pre[a[5]] = 2)
  7. 状态转移
    f [ l ] [ r ] = { m i n ( f [ l ] [ r ] , f [ l ] [ k ] + f [ k + 1 ] [ r ] ) a[k] == a[r]) m i n ( f [ l ] [ r ] , f [ l + 1 ] [ r ] + 1 , f [ l ] [ r − 1 ] + 1 ) else f[l][r] = \begin{cases}min(f[l][r], f[l][k] + f[k + 1][r]) & \text{a[k] == a[r])}\\ min(f[l][r], f[l + 1][r] + 1, f[l][r - 1] + 1) & \text{else}\end{cases} f[l][r]={min(f[l][r],f[l][k]+f[k+1][r])min(f[l][r],f[l+1][r]+1,f[l][r1]+1)a[k] == a[r])else
    • 前者表示区间[l, r - 1]存在和a[r]相同的a[k], 后者表示不存在这样的a[k]
    • 存在a[k] = a[r]时:区间[l, k]涂为a[k], 区间[k + 1, r]涂为a[r]即可(由于a[k] = a[r],所以这样可以减少一次 操作)
    • 不存在a[k] = a[r]时:那么f[l, r]可直接由区间长度比[l, r]少1的区间[l, r - 1]或者[l + 1, r]转移过来

CODE

const int N = 5e3 + 6;
int f[N][N];
int a[N], mp[N], pre[N];

int main()
{
	IOS;
	int T; cin >> T;
	while (T--)
	{
		int n; cin >> n;
		int m = 0;
		for (int i = 1; i <= n; i++)
		{
			cin >> a[i];
			if (a[i] == a[i - 1])
                i--, n--;
            else m++;
		}
        
		for (int i = 1; i <= m; i++)
			mp[i] = pre[i] = 0;
		for (int i = 1; i <= m; i++)
		{
			pre[i] = mp[a[i]];
			mp[a[i]] = i;
		}
		
		for(int i = 1; i <= m; i++)
			for (int j = 1; j <= m; j++)
			{
				if (i == j) f[i][i] = 0;
				else f[i][j] = inf;
			}
		for (int len = 2; len <= m; len++)
		{
			for (int l = 1; l <= m - len + 1; l++)
			{
				int r = l + len - 1;
				int& x = f[l][r];
				
				//若不存在与a[r]相同的颜色,则直接转移即可
				if(a[l] != a[r])
                    x = min({ x, f[l + 1][r] + 1, f[l][r - 1] + 1 });
				//存在与a[r]相同的颜色,则减少其操作次数
				for (int k = pre[r]; k >= l; k = pre[k])
					x = min(x, f[l][k] + f[k + 1][r]);
			}
		}
		
		cout << f[1][m] << endl;
	}

	return 0;
}

总结

dp最重要的还是要定义好状态以及不漏的将状态转移。看了好多篇题解,都是写的一知半解(幽灵),而且状态转移的时候都不太清楚,转移方程都有交集。。所以我费了好久写了一篇自己的理解。。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

to cling

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值