P2943 [USACO09MAR]Cleaning Up G

知识点:DP,结论

简述

原题面:Luogu

给定一长度为 \(n\) 的数列 \(a\),给定参数 \(m\),满足 \(1\le a_i\le m\)
要求将其划分为任意段,每段的代价为该段内权值种类数的平方。
最小化代价之和。
\(1\le a_i\le m\le n\le 4\times 10^4\)
1S,128MB。

分析

40 pts

显然 DP,设 \(f_{i}\) 表示划分 \(1\sim i\) 的最小代价之和。
每个数都分一段代价最大,初始化 \(\forall 1\le i\le n,\ f_{i} = i\),转移时枚举最后一段,则有:

\[f_{i} = \min_{j=1}^{i - 1} \left\{ f_{j} + \left| \bigcup_{k=j +1}^{i} \{a_i\}\right|^2\right\} \]

答案即 \(f_n\)
预处理区间权值种类数,总复杂度 \(O(n^2)\) 级别。

100 pts

由于分段的最大代价为 \(n\),则不可能存在一段的权值种类数 \(\sqrt n\)
则上述转移方程中 \(\left| \bigcup_{k=j +1}^{i} \{a_i\}\right|\ge \sqrt n\) 的转移都是无用的,可以考虑仅记录有贡献的转移前驱。
考虑在枚举 \(i\) 的同时维护 \(\operatorname{pos}_{j}\),记录满足 \([\operatorname{pos}_j,i]\) 中有 \(j\) 种权值的最左侧的位置,转移方程变为:

\[f_i = \min_{j=1}^{\sqrt n} \left\{ f_{\operatorname{pos}_j} + j^2\right\} \]

转移的时间复杂度为 \(O(n\sqrt n)\)


再考虑如何维护 \(\operatorname{pos}\),考虑维护 \(\sqrt n\) 个计数数组 \(\operatorname{cnt}\),第 \(j\) 个计数数组维护有 \(j\) 种权值的区间 \([\operatorname{pos}, i]\) 内各种的权值的个数。
每次更新 \(i\) 后,考虑将 \(a_i\) 加入所有计数数组后产生的影响。对于 \(\operatorname{cnt}_j\),讨论一下:

  • \(a_i\)\(\operatorname{cnt}_j\) 中出现过,不会影响权值种类数。
  • \(a_i\) 未出现过,且区间权值种类数 \(\ge j\),则令 \(\operatorname{pos}_j\) 不断右移,直至 \(\operatorname{cnt}_j\) 中出现新的一种权值的数量为零,以保证权值种类数为 \(j\)

\(\operatorname{pos}\) 仅会右移 \(n\) 次,显然上述维护过程的总时空复杂度均为 \(O(n\sqrt n)\)
程序总时空复杂度均为 \(O(n\sqrt n)\)

实现

//知识点:贪心,DP 
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 4e4 + 10;
//=============================================================
int n, m, sqrtn, a[kN], f[kN];
int pos[kN], num[210], cnt[210][kN];
//=============================================================
inline int read() {
  int f = 1, w = 0;
  char ch = getchar();
  for (; !isdigit(ch); ch = getchar())
    if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) {
    w = (w << 3) + (w << 1) + (ch ^ '0');
  }
  return f * w;
}
void Chkmax(int &fir_, int sec_) {
  if (sec_ > fir_) fir_ = sec_;
}
void Chkmin(int &fir_, int sec_) {
  if (sec_ < fir_) fir_ = sec_;
}
void Update(int id_, int val_) {
  cnt[id_][val_] ++;
  if (cnt[id_][val_] == 1) {
    ++ num[id_];
    while (num[id_] > id_) {
      cnt[id_][a[pos[id_]]] --;
      num[id_] -= !cnt[id_][a[pos[id_]]];
      ++ pos[id_];
    }
  }
}
//=============================================================
int main() {
  n = read(), m = read(), sqrtn = sqrt(n);
  for (int i = 1; i <= n; ++ i) a[i] = read(); 
  
  for (int i = 1; i <= sqrtn; ++ i) pos[i] = 1;
  for (int i = 1; i <= n; ++ i) {
    f[i] = i;
    for (int j = 1; j <= sqrtn; ++ j) {
      Update(j, a[i]);
      Chkmin(f[i], f[pos[j] - 1] + j * j);
    }
  }
  printf("%d\n", f[n]);
  return 0;
}
posted @ 2020-12-02 21:46  Luckyblock  阅读(121)  评论(0编辑  收藏  举报