CF833B The Bakery 题解

一道简单 ds 优化 dp 题,下无特殊说明则 \(k\) 不是题目中的 \(k\)

朴素方程:设 \(f_{i,j}\) 表示将 \([1,i]\) 内的所有数划分成 \(j\) 段的方案数,那么有转移方程:

\[f_{i,j}=\max\{f_{k,j-1}+val(k+1,i) \mid k\le i\} \]

也就是将 \([k+1,i]\) 划成一段,\(val(k+1,i)\)\(k=i\) 时该值为 0) 表示 \([k+1,i]\) 内不同 \(a_i\) 数量,复杂度 \(O(n^3k)\),如果 \(j\) 倒序枚举可以做到 \(O(n^2k)\)(两个复杂度中的 \(k\) 是题中的 \(k\)),考虑优化。

先预处理一个 \(Pre_i\) 表示 \(a_{Pre_i}=a_i\)\(Pre_i\) 最大,那么只有满足 \(k \in [Pre_i+1,i]\)\(val(k+1,i)\) 会被影响到,有个 1 的贡献。

据此我们考虑先枚举 \(j \in [1,k]\) 为划分段数,然后对于 \(f_{i,j-1}\) 建线段树,线段树区间中 \(x\) 位置表示 \(f_{x,j-1}\)

根据上文所述,\(i\) 会影响到 \(k \in [Pre_i+1,i]\) 的 dp 值,于是我们先对区间 \([Pre_i,i-1]\) 区间加 1,左右端点减 1 的原因是转移方程的 \(val\)\(k\) 加了 1。

然后对于 \(f_{i,j}\) 查询 \([k-1,i-1]\)(注意左端点不能是 1)内的最大值即可。

答案 \(f_{n,k}\)(这个 \(k\) 和复杂度的 \(k\) 是题中的 \(k\)),初值全 0,复杂度 \(O(nk \log n)\),没必要滚动数组。

GitHub:CodeBase-of-Plozia

Code:

/*
========= Plozia =========
	Author:Plozia
	Problem:CF833B The Bakery
	Date:2022/4/29
========= Plozia =========
*/

#include <bits/stdc++.h>

typedef long long LL;
const int MAXN = 3.5e4 + 5;
int n, k, a[MAXN], f[MAXN][55], Pre[MAXN], book[MAXN];
struct node
{
	int tag, Maxn;
}tree[MAXN << 2];
#define tag(p) tree[p].tag
#define Maxn(p) tree[p].Maxn

int Read()
{
	int sum = 0, fh = 1; char ch = getchar();
	for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1;
	for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = sum * 10 + (ch ^ 48);
	return sum * fh;
}
int Max(int fir, int sec) { return (fir > sec) ? fir : sec; }
int Min(int fir, int sec) { return (fir < sec) ? fir : sec; }

void Update(int p) { Maxn(p) = Max(Maxn(p << 1), Maxn(p << 1 | 1)); }
void Spread(int p)
{
	if (tag(p))
	{
		Maxn(p << 1) += tag(p); Maxn(p << 1 | 1) += tag(p);
		tag(p << 1) += tag(p); tag(p << 1 | 1) += tag(p);
		tag(p) = 0;
	}
}

void Change(int p, int x, int v, int lp, int rp)
{
	tag(p) = 0; if (lp == rp) { Maxn(p) = v; return ; }
	int mid = (lp + rp) >> 1;
	if (x <= mid) Change(p << 1, x, v, lp, mid);
	else Change(p << 1 | 1, x, v, mid + 1, rp);
	Update(p);
}
void Add(int p, int l, int r, int v, int lp, int rp)
{
	if (lp >= l && rp <= r) { Maxn(p) += v; tag(p) += v; return ; }
	Spread(p); int mid = (lp + rp) >> 1;
	if (l <= mid) Add(p << 1, l, r, v, lp, mid);
	if (r > mid) Add(p << 1 | 1, l, r, v, mid + 1, rp);
	Update(p);
}
int Ask(int p, int l, int r, int lp, int rp)
{
	if (lp >= l && rp <= r) return Maxn(p);
	Spread(p); int mid = (lp + rp) >> 1, val = 0;
	if (l <= mid) val = Max(val, Ask(p << 1, l, r, lp, mid));
	if (r > mid) val = Max(val, Ask(p << 1 | 1, l, r, mid + 1, rp));
	return val;
}

int main()
{
	n = Read(), k = Read(); for (int i = 1; i <= n; ++i) a[i] = Read();
	for (int i = 1; i <= n; ++i)
	{
		if (!book[a[i]]) book[a[i]] = i;
		else { Pre[i] = book[a[i]]; book[a[i]] = i; }
	}
	for (int j = 1; j <= k; ++j)
	{
		for (int i = 1; i <= n; ++i) Change(1, i, f[i][j - 1], 0, n);
		for (int i = 1; i <= n; ++i)
		{
			Add(1, Pre[i], i - 1, 1, 0, n);
			f[i][j] = Ask(1, j - 1, i - 1, 0, n);
		}
	}
	printf("%d\n", f[n][k]); return 0;
}
posted @ 2022-04-29 20:17  Plozia  阅读(94)  评论(2编辑  收藏  举报