题意

你有n个数字,范围[1, m],你可以选择其中的三个数字构成一个三元组,但是这三个数字必须是连续的或者相同的,每个数字只能用一次,问这n个数字最多构成多少个三元组?

分析

这里想谈一下DP的一个套路,捆绑
有的DP题目,它可能会要求和一些东西捆绑,求方案数,这种时候如何单点设置状态呢?
以这个题为例,只考虑前i种数字,对于数字i,它可能 (i,i,i) (i-2,i-1,i) (i-1,i) (i,) 如果是后两种,它还未构成一个合法的序列,我们还不知道是否存在i+1来完善它
也就是考虑前i种数字的时候,后两种不应该被计入方案数里,但转移的时候我们要用到他们的数量,这里就可以再用两维将他的数量捆绑起来一起转移
状态
dp[i][j][k] 表示只考虑前i种数字,捆绑了j个(i-1,i) k个(i)的最大方案数
初始条件
dp[0][0][0] = 0 ,others = -inf
因为假设只考虑前0种数字(实际不存在,假定数量为0),它无法构成任何三元组,但是转移到前1种可以用前0种转移过来
其余的都是未求解状态 ,默认为负无穷
转移方案
我们有a[i+1]个i+1
考虑如何从dp[i][j][k]转移到前i+1个去
考虑第一个贪心策略:如果有未完成的三元组,优先补全它,因为如果不补全,它便会向后需求,这样无论如何也不会让答案更大
所以我们需要用j+k个i+1 去补全(i-1,i)和(i)这样我们形成了j个(i-1,i,i+1)可以计入方案数里,以及k个(i,i+1)
还剩下a[i+1] - j - k个i+1
枚举我们捆绑多少个(i+1),将剩下的全部变成(i+1,i+1,i+1)
这样转移的方程就确定了
dp[i+1][k][t] = max ( dp[i+1][k][t] , dp[i][j][k] + j + (a[i+1]-j-k-t)/3)
最终答案就是只考虑前m个数字,最大的答案
考虑第二个贪心策略,如果我们够造了三个顺子,他们其实可以重组成三个同花,也就是说,j,k一定小于3

代码

#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[1000005];
int dp[1000005][3][3];
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int tmp;
    cin>>n>>m;
    for(int i=1;i<=n;i++) {
    cin>>tmp;
    a[tmp]++;
    }
    memset(dp,0x80,sizeof(dp));
    dp[0][0][0] = 0;
    for(int i=0;i<m;i++) {
    for(int j=0;j<3;j++) {
        for(int k=0;k<3;k++) {
        int res = a[i+1] -j - k;
        for(int t=0;t<3&&t<=res;t++) {
            dp[i+1][k][t] = max(dp[i+1][k][t],dp[i][j][k]+j+(res-t)/3);
        }
        }
    }
    }
    int ans =0 ;
    for(int i=0;i<3;i++) for(int j=0;j<3;j++) ans = max(ans,dp[m][i][j]);
    cout<<ans<<endl;

}