题目
n张牌,m种牌,每个牌上一个号
可以连续打三个(3 3 3),也可以连续打顺子(1 2 3)
问最多能打多少组
思路来源
马老师&&航神
题解
首先如果连续3个都有三个,打三个顺和三个仨是一样的,成组消去
打3个相同的也是足够优的,但不是绝对的,
可以证明(好吧我证明不了),
每个拿出6个来专门用于凑顺,
最多给上上个提供2个,上个提供2个,自己开头提供2个
这样足够了,剩下多的用于打仨就行了
dp[i+1][j][k]代表对于值为i的数来说,专门拿出j个去凑以i-1开头的顺子,专门拿出k个去凑以i开头的顺子
dp[i+1][k][l]=max(dp[i][j][k]+(num[i]-j-k-l)/3+l,dp[i+1][k][l]);
dp[下一个值][当前值挪出来凑以上一个值开头的顺子的个数][当前值挪出来凑当前值开头的顺子的个数]
j k l就分别是挪出来凑上上个值 上个值 和当前值的个数
为了避免重复统计,只把这些顺子统计在那个开头的值里面,所以加上l
而最后dp[m+1][0][0]显然对于i=m而言,i-1和i都凑不出顺子了(因为没有i+1),所以均0是最优的
心得
感觉是三步三步滑动的这样一个状态转移dp
其实自己写wa贪心代码的时候也考虑着
如果出现1 3 2,1 2 3,1 2 1,2 2 3,这些连续的三个的情况该怎么搞……
然后还分类讨论贪心来着,
现在看来,暴力让它们一起往后推,是为dp。
代码
滚动数组要清零,不然受i-2那个结果影响。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn=1e6+10;
int dp[2][3][3];
int num[maxn],n,m;
int main()
{ scanf("%d%d",&n,&m);for(int i=0;i<n;++i){int x;scanf("%d",&x);num[x]++; }for(int i=1;i<=m;++i){ for(int k=0;k<3;++k){for(int l=0;l<3;++l){dp[(i+1)%2][k][l]=0;}}for(int j=0;j<3;++j){for(int k=0;k<3;++k){for(int l=0;l<3;++l){if(num[i]<j+k+l)continue;dp[(i+1)%2][k][l]=max(dp[i%2][j][k]+(num[i]-j-k-l)/3+l,dp[(i+1)%2][k][l]);}}} }printf("%d\n",dp[(m+1)%2][0][0]); return 0;
}