P3558 [POI2013]BAJ-Bytecomputer


知识点:结论,线性 DP

原题面:Luogu


题意简述

给定一只包含 \(-1.0,1\) 的数列 \(a\),每次操作可令 \(a_{i} + a_{i-1}\)
判断能否使该序列单调不降,若可以则求所需的最少操作次数。
\(1\le n\le 10^6\)\(|a_i|\le 1\)


分析题意

发现一些结论:

  • 显然应该从前往后操作。
  • 若某位置为 \(1\),则经过若干次操作后它之后的位置都可以 \(\ge 1\)
  • 若某位置为 \(-1\),则经过若干次操作后它之后的位置都可以 \(\le -1\)
  • 由上述结论可知,若出现:\(0,\cdots, 0, -1,\cdots\) 的情况,则一定无解,否则一定有解。
  • 显然最终答案一定是 \(-1\cdots, -1,0,\cdots,0,1\) 的形式,不可能出现 \(|a_i|\ge 2\) 的情况。 因为 \(-2\) 只能通过 \(-1-1\) 得到,此时已经单调不降了,变为 \(-2\) 一定不优。\(2\) 同理。

根据结论 4 判下无解,由结论 5,考虑 DP。
\(f_{i,-1/0/1}\) 表示当前操作到第 \(i\) 个数,第 \(i\) 个数变为 \(-1/0/1\) 时,令前 \(i\) 个数单调不降的最小操作次数。
初始化 \(f_{1,a_1} = 0\),转移时分类讨论:

\[\begin{aligned} f_{i,-1} &= \begin{cases} f_{i-1,-1} &(a_i = -1)\\ f_{i-1,-1} + 1 &(a_i = 0)\\ f_{i-1,-1} + 2 &(a_i = 1) \end{cases}\\ f_{i,0} &= \begin{cases} \min(f_{i-1,-1}, f_{i-1,0}) &(a_{i}=0)\\ f_{i-1,-1} + 1 &(a_{i} = 1) \end{cases}\\ f_{i,1} &= \begin{cases} f_{i-1,1} + 2 &(a_i = -1)\\ f_{i-1,1} + 1 &(a_i = 0)\\ \min(f_{i-1,-1},f_{i-1,0},f_{i-1,1}) &(a_i = 1) \end{cases} \end{aligned}\]

答案即 \(\min(f_{n,-1},f_{n,0},f_{n,1})\)

总时间复杂度 \(O(n)\)


爆零小技巧

当然不可能真的用负数当下标啦!


代码实现

//知识点:线性 DP 
/*
By:Luckyblock
*/
#include 
#include 
#include 
#include 
#define LL long long
const int kN = 1e6 + 10;
//=============================================================
int n, ans, a[kN], f[kN][3];
//=============================================================
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_;
}
//=============================================================
int main() {
  n = read();
  int fir1 = 0, firm1 = 0;
  for (int i = 1; i <= n; ++ i) {
    a[i] = read();
    if (!fir1 && a[i] == 1) fir1 = i;
    if (!firm1 && a[i] == -1) firm1 = i;
  }
  if (1 < firm1 && firm1 < fir1) {
    printf("BRAK\n");
    return 0; 
  }
  
  memset(f, 63, sizeof (f));
  f[1][a[1] + 1] = 0;
  for (int i = 2; i <= n; ++ i) {
    f[i][0] = f[i - 1][0] + a[i] + 1;
    
    if (a[i] == 0) {
      f[i][1] = std::min(f[i - 1][0], f[i - 1][1]);
    } else if (a[i] == 1) {
      f[i][1] = f[i - 1][0] + 1;
    }
    
    if (a[i] != 1) {
      f[i][2] = f[i - 1][2] + 1 - a[i];
    } else {
      Chkmin(f[i][2], f[i - 1][0]);
      Chkmin(f[i][2], f[i - 1][1]);
      Chkmin(f[i][2], f[i - 1][2]);
    }
  }
  
  ans = f[n + 1][0];
  Chkmin(ans, f[n][0]);
  Chkmin(ans, f[n][1]);
  Chkmin(ans, f[n][2]);
  if (ans == f[n + 1][0]) {
    printf("BRAK\n");
  } else {
    printf("%d\n", ans);  
  }
  return 0;
}