Codeforces#721 E

题目链接

  题意:将一个序列分成\(k\)段,求出这\(k\)段中\(cost(t) = \sum\limits_{x \in set(t)} last(x) - first(x)\)的大小,也就是求出区间内最后一次出现的数和第一次出现的数之间的差值。
  思路:
  对于求解这个式子的最小值,考虑用\(dp\)来求解,\(dp\left[i\right]\left[j\right]\)表示在下标为\(j\)的位置之前分成了\(i\)段,从而有了\(dp\left[i\right]\left[j\right] = \min\limits_{k < j} \{ dp[\left[i - 1\right]\left[k\right] + w\left[k + 1\right]\left[j\right]\}\),如果暴力的去求解这个式子的话会因为后面\(w\left[k + 1\right]\left[j\right]\)这个式子超时,所以重点就是要优化这个式子。
  \(last(x) - first(x)\)可以拆成\(last(x) - prev(x) + \left(prev\left(x\right) - first\left(x\right)\right)\),也就是我们去求这个式子的时候可以用前缀和的操作来进行优化。而每一次求解\(\min\)的操作的时候,同时需要找到区间最小值来是的前缀和最小,那么就可以考虑用线段树来维护。

	int n, m;
	std::cin >> n >> m;

	std::vector<int> pre(n + 1), last(n + 1), a(n + 1);
	for (int i = 1; i <= n; i++ ) {
		std::cin >> a[i];
		last[i] = pre[a[i]];
		pre[a[i]] = i;
	}

	std::vector<int> dp(n + 1, 2E9);
	dp[0] = 0;
	for (int i = 0; i < m; i ++ ) {
		Seg SGT(n + 1);
		for (int i = 0; i <= n; i++ ) {
			SGT.change(1, 0, n, i, i, dp[i]);
		}
		for (int i = 1; i <= n; i++ ) {
			if (last[i]) SGT.change(1, 0, n, 0, last[i] - 1, i - last[i]);
			dp[i] = SGT.query(1, 0, n, 0, i - 1);
		}
	}

	std::cout << dp[n] << "\n";

posted @ 2022-08-12 21:28  浅渊  阅读(26)  评论(0)    收藏  举报