把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【CF1479B2】Painting the Array II(简单DP)

点此看题面

  • 给定一个长度为\(n\)的数组,每个位置上有一个颜色。
  • 你需要把它划分成两个子序列,求这两个子序列中颜色段数之和的最小值。
  • \(n\le10^5\)

动态规划

考虑分配完第\(i\)个元素之后,两个子序列中一个末尾肯定是\(a_i\),而另一个末尾我们不妨假设为\(j\)

于是设\(f_{i,j}\)表示分配完第\(i\)个元素,另一个子序列末尾是\(j\)时,之前颜色段数之和的最小值。

转移分两种:

  • \(a_i\)放在\(a_{i-1}\)后面,则\(f_{i,j}=f_{i-1,j}+[a_{i}\not=a_{i-1}]\)
  • \(a_i\)放在\(j\)后面,则\(f_{i,a_{i-1}}=f_{i-1,j}+[a_i\not=j]\)

于是这样就能得到一个\(O(n^2)\)的暴力\(DP\)

简单的优化

如果数据结构学傻的话看到这种式子可能直接就上线段树了。。。(当然也不是不可以)

然而我们发现,第一种转移实际上是全局修改,第二种转移其实可以拆成全局询问最小值和对\(f_{i-1,a_i}\)的单点询问、对\(f_{i,a_{i-1}}\)的单点修改。

全局修改直接记录一个全局\(tag\)就可以了。由于除去\(tag\)之后所有元素都只会越来越小,因此全局询问只需要维护一个全局最小值

代码:\(O(n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
using namespace std;
int n,a[N+5],f[N+5];
int main()
{
      RI i;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i),f[i]=1e9;//初始化
      RI t,tg=0,Mn=0;for(i=1;i<=n;++i) t=min(Mn+tg+1,f[a[i]]+tg),//t记录第二种转移值
            a[i-1]^a[i]&&++tg,f[a[i-1]]+tg>t&&(Mn=min(Mn,f[a[i-1]]=t-tg));//tg记录第一种转移的全局标记,Mn记录全局最小值
      return printf("%d\n",Mn+tg),0;//答案就是最小值+全局标记
}
posted @ 2021-02-08 10:09  TheLostWeak  阅读(213)  评论(0编辑  收藏  举报