【好题选讲】DP 阴间题选讲 I

ABC176F

Brave CODE

暴力比较简单,观察到下个状态只会关心上次决策剩下的两个字符是什么,于是设 \(dp_{i,x,y}\) 表示进行完第 \(i\) 次决策,剩下了字符 \(x,y\) 的最大分数。这里显然 \(x,y\) 的顺序不重要,因为我们可以任意重排。

转移方程需要一些分类讨论,设我们接下来拿到的三个牌为 \(p,q,r\) ,枚举 \(dp_{i-1}\)\(x,y\),那么有这么几种情况:

  • 如果 \(p=q=r\),此时我们把 \(p,q,r\) 扔掉一定是最优的,那么对于所有的 \(x,y\),都有 \(dp_{i,x,y}=dp_{i-1,x,y}+1\)

    为什么最优呢?如果这样不是最优的,那么就是要把 \(p,q,r\) 拆开给后面的 \(6\) 个和他们相等的字符匹成 \(3\) 个贡献,但是显然我们至多保留它们中相等的 \(2\) 个字符,因为我们要删去 \(3\) 个字符。那我现在匹配和之后让它们 \(6\) 个自己匹配当然是更优的。

  • 如果 \(p,q,r\) 中存在任意两个字符相等。

    • 如果 \(p=q\),此时固定 \(x=p=q\),对于所有 \(y\)\(dp_{i,y,r} = dp_{i-1,x,y}+1\)

      对于所有 \(x\ne p=q\),若 \(x=r\)\(y=r\),此时有 \(dp_{i,p,q}=dp_{i-1,x,y}+1\)

      \(x\ne p,q,r\),此时对于所有的 \(y\)\(dp_{i-1,x,y}\) 显然不会给 \(dp_{i+1}\) 的任何位置产生任何贡献。

    • 如果 \(p=r\),交换 \(q,r\) 变为第一种 \(p,q,r\) 中存在任意两个字符相等的情况。

    • 如果 \(q=r\),交换 \(p,r\) 变为第一种 \(p,q,r\) 中存在任意两个字符相等的情况。

      你看这里我都把这些情况列了一遍,其他题解好像一句等价就完事了

  • 如果 \(p,q,r\) 两两不相等,考虑让 \(x=y\) 分别等于 \(p,q,r\),于是有 \(dp_{i,q,r}=dp_{i-1,p,p}+1\)\(dp_{i,p,r}=dp_{i-1,q,q}+1\)\(dp_{i,p,q}=dp_{i-1,r,r}+1\)

  • 以上是所有会导致得分的情况,下面对于任意情况的 \(p,q,y\) 都适用,考虑枚举 \(y\),分别考虑保留 \(p,y\)\(q,y\)\(r,y\) 的情况:

    • 若保留 \(p,y\),有 \(dp_{i,p,y}=\max_{x=1}^{n}dp_{i-1,x,y}\)
    • 若保留 \(q,y\),有 \(dp_{i,q,y}=\max_{x=1}^{n}dp_{i-1,x,y}\)
    • 若保留 \(r,y\),有 \(dp_{i,r,y}=\max_{x=1}^{n}dp_{i-1,x,y}\)
  • 最后,分别考虑保留 \(p,q\)\(p,r\)\(q,r\) 的情况:

    • 若保留 \(p,q\),有 \(dp_{i,p,q}=\max_{x=1}^{n}\max_{y=1}^{n} dp_{i,x,y}\)
    • 若保留 \(p,r\),有 \(dp_{i,p,r}=\max_{x=1}^{n}\max_{y=1}^{n} dp_{i,x,y}\)
    • 若保留 \(q,r\),有 \(dp_{i,q,r}=\max_{x=1}^{n}\max_{y=1}^{n} dp_{i,x,y}\)

在所有转移中,全局加 \(1\) 和枚举找最大值的那些情况的转移是 \(O(n^2)\) 的,故这个算法的时间复杂度是 \(O(n^3)\),关于空间复杂度,可以通过两个数组互相存上次信息的方式优化到 \(O(n^2)\)

\(O(n^3)\) 显然是过不了这个题的,考虑优化。

首先全局加 \(1\) 不会影响原来 \(dp\) 数组中的相对大小关系,我们把这个全局加单独开个变量记录下,输出答案时一并加上即可。

你会发现那些枚举最大值的情况都很奶糖啊,都是在整行整列的找最大值,有的只是要找个全局最大值,我们能把这些东西预处理下吗?

当然可以,我们另开个数组 linemax[x] 表示 \(x\) 行的最大值。于是就可以 \(O(n)\) 转移了。

但让两个数组来回滚动不还是 \(O(n^2)\) 的吗?你会发现如果去除了那个全局加 \(1\) 操作,剩下操作对 \(dp_{i}\) 的修改次数是 \(O(n)\) 的,于是我们考虑把所有的修改操作存下来,\(O(n)\) 的从 \(dp_{i-1}\) 直接覆盖到 \(dp_{i}\)

怎么写成 ds 了

覆盖时由于我们知道覆盖的是那一行,因此上文提到的行最大值,全局最大值都是可以维护的。

单次转移变成了 \(O(n)\),做完了!

code

Show me the code
#define psb push_back
#define mkp make_pair
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
  ll x=0,f=1;
  char c=getchar();
  while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
  while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
  return x*f;
}
const int N=3005;
int c[N*4];
struct work{
  int x,y;
  int var;
};
int dp[N][N];
int linemax[N];
int global_plus;
int global_max;
int main(){
  
  int n;cin>>n;
  for(int i=1;i<=3*n;i++)cin>>c[i];
  vector<work> upd;
  for(int i=0;i<=n;i++){
    linemax[i]=-73357733;
    for(int j=0;j<=n;j++){
      dp[i][j]=-73357733;
    }
  }
  dp[c[1]][c[2]]=dp[c[2]][c[1]]=0;
  linemax[c[1]]=0;linemax[c[2]]=0;
  for(int i=3;i<3*n;i+=3){
    upd.clear();
    int p=c[i],q=c[i+1],r=c[i+2];
    if(p==q&&q==r){
      global_plus++;
      continue;
    }
    if(p==q||p==r||q==r){
      if(p==r)swap(q,r);
      if(q==r)swap(p,r);
      for(int y=1;y<=n;y++)upd.push_back({r,y,dp[p][y]+1});
      upd.push_back({p,p,dp[r][r]+1});
    }
    else{
      upd.push_back({p,q,dp[r][r]+1});
      upd.push_back({p,r,dp[q][q]+1});
      upd.push_back({r,q,dp[p][p]+1});
    }
    for(int y=1;y<=n;y++){
      upd.push_back({p,y,linemax[y]});
      upd.push_back({q,y,linemax[y]});
      upd.push_back({r,y,linemax[y]});
    }
    upd.push_back({p,q,global_max});
    upd.push_back({p,r,global_max});
    upd.push_back({q,r,global_max});

    for(work info:upd){
      int x=info.x,y=info.y;
      int var=info.var;
      dp[x][y]=dp[y][x]=max({dp[x][y],dp[y][x],var});
      global_max=max(global_max,dp[x][y]);
      linemax[x]=max(linemax[x],var);
      linemax[y]=max(linemax[y],var);
    }
  } 
  cout<<max(global_max,dp[c[3*n]][c[3*n]]+1)+global_plus;

  
  return 0;
}

CF2B

posted @ 2025-06-08 06:48  hm2ns  阅读(21)  评论(0)    收藏  举报