【题解】CF1110D Jongmah(DP)

【题解】CF1110D Jongmah

代码很短,但是思路我怎么也想不到的神仙 DP。

题目链接

CF1110D Jongmah

题意概述

你在玩一个叫做 Jongmah 的游戏,你手上有 nn 个麻将,每个麻将上有一个在 11mm 范围内的整数 aia_i

为了赢得游戏,你需要将这些麻将排列成一些三元组,每个三元组中的元素是相同的或者连续的。如 7,7,77,7,712,13,1412,13,14 都是合法的。你只能使用手中的麻将,并且每个麻将只能使用一次。

请求出你最多可以形成多少个三元组。

数据范围

  • 1n,m1061 \le n,m \le 10^6
  • 1aim1 \le a_i \le m

思路分析

首先我们定义第 ii 个顺子表示三个麻将上写着 {i,i+1,i+2}\{i,i+1,i+2\} 的三元组,ii 的三连击表示三个麻将上都写着 ii 的三元组,cnticnt_i 表示写着 ii 的麻将数。

那么有一个结论是:对于每个 i(1im)i(1 \le i \le m),第 ii 个顺子最多只需要两次。因为 33 个第 ii 个顺子可以转化为 33 个三连击(i,i+1,i+2i,i+1,i+2 的三连击),所以一定存在最优解满足第 ii 个顺子取不超过两次。这是麻将题的常见技巧。

那么我们就可以考虑枚举第 ii 个顺子选了多少次(0,10,122 次)。

如果直接枚举每个顺子选了多少次,是 3m3^m 的复杂度,不能接受。考虑通过 DP 优化。

但是如果直接 DP,发现很难进行状态设计,那么我们可以考虑简化问题。

考虑先解决如下问题:

如果我们并不是可以选择每个顺子,而是只能选择第 ii 个顺子(其中 i mod3=1i\bmod 3=1),那么如何确定有多少种合法的选择方案。

这个问题的简化之处就在于,第 ii 个的顺子的选择不受任何其它顺子的限制。

那么我们可以直接定义 dpidp_i 表示选到了第 ii 个顺子总共有多少种三元组的选择情况。然后枚举选择次数 kk,则对于每个 ii,选择 ii 的三连击的个数是 cntik3\left \lfloor\dfrac{cnt_i-k}{3}\right\rfloor,那么 dpidp_i 就可以用 dpi3dp_{i-3} 加上第 ii 个顺子的选择情况再加上 i,i1,i2i,i-1,i-2 的三连击的选择情况而得到,那么有:

dpi=max0k2{dpi3+k+cntik3+cnti1k3+cnti2k3}dp_{i}=\max\limits_{0 \le k \le 2}\{dp_{i-3}+k+\left \lfloor\dfrac{cnt_i-k}{3}\right\rfloor+\left \lfloor\dfrac{cnt_{i-1}-k}{3}\right\rfloor+\left \lfloor\dfrac{cnt_{i-2}-k}{3}\right\rfloor\}

这是只能选择第 i(i mod3=1)i(i\bmod 3=1) 个顺子的情况。


那么对于所有 1im1\le i \le m 的每一个 ii 都能选的情况呢?

可以发现这个时候第 ii 个的顺子的选择情况和 ii 的三连击的选择情况,都受到第 i1i-1 个顺子的选择情况和第 i2i-2 的顺子选择情况的影响,这个时候我们就不能直接定义 dpidp_i 表示选到了第 ii 个顺子的方案来解决问题了。我们考虑将第 i1i-1 个顺子的选择情况和第 i2i-2 个顺子的选择情况加进状态,由于这里第 ii 个顺子的选择情况也会影响到后面第 i+1i+1 个顺子的选择情况,所以也要把它加进状态,即 dpi,j,k,tdp_{i,j,k,t} 表示考虑到了第 ii 个顺子,第 ii 个顺子选择了 jj 次,第 i1i-1 个顺子选择了 kk 次,第 i2i-2 个顺子选了 tt 次的方案数,那么这时候 dpi,j,k,tdp_{i,j,k,t} 就可以直接由 dpi1,k,t,ldp_{i-1,k,t,l} 其中 ll 是第 i3i-3 个顺子的选择情况。

这时候我们发现,第 i3i-3 个顺子的选择情况,对第 ii 个顺子的选择情况和 ii 的三连击的选择情况没有影响。同时在 dpi1,k,t,ldp_{i-1,k,t,l} 的状态里已经包含了第 i2i-2 个顺子的选择情况,所以我们可以将状态的最后一维删去。那么我们最终确定的 dp 状态就是:dpi,j,kdp_{i,j,k} 表示考虑到了第 ii 个顺子,第 ii 个顺子选择了 jj 次,第 i1i-1 个顺子选择了 kk 次的方案数是多少。

那么对于 dpi,j,kdp_{i,j,k},就可以由 dpi1,k,tdp_{i-1,k,t} 转移而来,其中 tt 表示第 i2i-2 个顺子选择了 tt 次。然后我们枚举 j,k,tj,k,t,那么 dpi,j,kdp_{i,j,k} 就等于 dpi1,k,tdp_{i-1,k,t} 加上第 ii 个顺子选择次数 jj 再加上 ii 的三连击的选择次数。


现在问题就转化为,如何在已知 j,k,tj,k,t 的情况下,求 ii 的三连击可以选择多少次。

假设 j=1,k=1,t=1j=1,k=1,t=1,如图所示:

由该图可知,红色的部分即为三连击的个数,即 cntijkt3\left\lfloor\dfrac{cnt_i-j-k-t}{3}\right\rfloor

那么总的转移方程式就为:

dpi,j,k=max{dpi1,k,t+j+cntijkt3}dp_{i,j,k}=\max\{dp_{i-1,k,t}+j+\left\lfloor\dfrac{cnt_i-j-k-t}{3}\right\rfloor\}

时间复杂度 O(27n)O(27n)

代码实现

代码
//CF1110D
//The Way to The Terminal Station…
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=1e6+10;
int dp[maxn][3][3],a[maxn],cnt[maxn];

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

int main()
{
	int n,m;
	n=read();m=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		cnt[a[i]]++;
	}
	for(int i=1;i<=m;i++)
	{
		for(int j=0;j<3;j++)
		{
			for(int k=0;k<3;k++)
			{
				for(int t=0;t<3;t++)
				{
					if(cnt[i]<j+k+t)continue;
					dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][t]+(cnt[i]-j-k-t)/3+j);
				}
			}
		}
	}
	cout<<dp[m][0][0]<<"\n";
	return 0;
}
posted @   向日葵Reta  阅读(45)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
历史上的今天:
2022-09-30 【题解】P2167 [SDOI2009]Bill的挑战(状压 DP)
点击右上角即可分享
微信分享提示