BCPC Winter Training5 TD
题意
长度n(35000)的数组,分成k(50)组,每组价值为数的种类数,问如何分组使总和最大。
思考
正常dp的话应该是dp[i][j]表示前j个人分成i段能获得的最大价值。
dp方程为
\[dp[i][j] = max_{k=i-1}^{j-1}\{dp[i - 1][k]+val(k+1,j)\}
\]
时间复杂度是O(n2k)的,需要优化。
优化: 可以发现在同一个i中,我们的j是一个一个加进去的。而且,在更新一个j的时候val(i,j)的变化就是j位置上这个数到他上一个位置这个区间有了更新(原先这些数到最后一个数之间没有这个数,但现在有了,所有以最后一个数作为末端的区间都能加一价值)。这样的话,我们可以用线段树来维护等式右边。
枚举i,枚举j每次加入j然后更新线段树。
更新方法:
- 加入dp[i-1][j-1],单点修改。
- 更新val,将当前数上一个位置到当前j的区间价值+1。
查询最大值,作为dp[i][j]。
代码
#include <bits/stdc++.h>
using namespace std;
int n, m;
int a[35010];
int dp[55][35010];
int tree[35010 << 3];
int lazy[35010 << 3];
int pre[35010];
void reset()
{
memset(tree, 0, sizeof(tree));
memset(lazy, 0, sizeof(lazy));
}
void push_down(int x)
{
tree[x << 1] += lazy[x];
tree[x << 1 | 1] += lazy[x];
lazy[x << 1] += lazy[x];
lazy[x << 1 | 1] += lazy[x];
lazy[x] = 0;
}
void add1(int num, int l, int r, int q, int c)
{
if (l == r)
{
tree[num] += c;
return ;
}
push_down(num);
int mid = l + r >> 1;
if (q <= mid)
add1(num << 1, l, mid, q, c);
else
add1(num << 1 | 1, mid + 1, r, q, c);
tree[num] = max(tree[num << 1], tree[num << 1 | 1]);
}
void add2(int num, int l, int r, int ql,int qr, int c)
{
if (ql <= l && r <= qr)
{
tree[num] += c;
lazy[num] += c;
return ;
}
push_down(num);
int mid = l + r >> 1;
if (ql <= mid)
add2(num << 1, l, mid, ql, qr, c);
if (qr > mid)
add2(num << 1 | 1, mid + 1, r, ql, qr, c);
tree[num] = max(tree[num << 1], tree[num << 1 | 1]);
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; ++i)
cin >> a[i];
for (int k = 1; k <= m; ++k)
{
reset();
for (int i = 1; i <= n; ++i)
pre[i] = k - 1;
for (int i = k; i <= n; ++i)
{
add1(1, 0, n, i - 1, dp[k - 1][i - 1]);
add2(1, 0, n, pre[a[i]], i - 1, 1);
dp[k][i] = tree[1];
pre[a[i]] = i;
}
}
cout << dp[m][n];
return 0;
}