做题随笔: P11323

Solution

闲话

蒟蒻第一次写题解,若有不当还请海涵。

题意

\(n\)类牌,每类\(v_i\)张,每次可出一张(单牌),同类的二张(对子),类A的三张和类B的一张(三带一),同类的四张(炸),求出完牌的最少出牌次数。

分析

逐类考虑:

首先,某类牌多于4张时无脑炸不是最优(本蒟蒻在这里先死了10分钟),只要看一眼样例的例一不难得出——

[1,1,1,1][1][2]:3步

[1,1,1,2][1,1]:2步

因此考虑拆掉:2+2完全亏,因此全部拆为1+3.

可以发现,三带一的方式是比较特殊的。容易想到:如果有能匹配的不匹配,则会造成2步浪费。故一定要优先匹配。但仅单牌和三个匹配可能有三个的数量多的情况,再考虑对子;

由拆炸为三带一自然想到对子可以拆成两个单牌:有三张相同和一个对子剩余时,拆2步,不拆3步,所以必拆补齐。(当然,无脑拆显然亏)

最后要是还有三个相同的剩余,考虑互相补足:只有\(1\times3\)直接出(2步),\(2\times3\)拆成三带一加一对(2步),\(3\times3\)为两个三带一加一张单牌,(3步)\(4\times3\)则为三个三带一(3步)。\(4\times3\)直接拆完,因此多于\(4\times3\)的情况\((4n+r)\times3\)步数为\(3n\)加上\(r\times3\)的步数。

于是此题得解。

实现

读入每类牌张数,直接全拆成3张的。

\(cnt_1\)+\(cnt_2\times2\)\(cnt_1\)\(cnt_3\)的大小写\(if\)条件即可。

详情请见代码——

Code

#include <iostream>
#include <cstdio>
#include <cctype>

using namespace std;

typedef long long ll;//开long long好习惯

inline ll fr() {
	ll x=0; char c=getchar();
	while(!isdigit(c)) c=getchar();//听说用isdigit()比char比大小快
	while(isdigit(c)) {
		x=(x<<3)+(x<<1)+(c^48);
		c=getchar();
	}
	return x;
}

const int maxn=1e6;
ll T,n,v,ans;
//T-数据组数 n-牌种数 v-当前牌种张数 ans-answer

int main() {
	T=fr();
	while(T--) {
		n=fr();
		ans=0;
		ll cnt[4]={0,0,0,0};//张数为下标的牌堆组数
		//多测不清空,直接见祖宗
		for(register int i = 0; i < n; i++) {
			v=fr();
			cnt[3]+=v/3;
			cnt[v%3]++;//直接分3
		}
		ll s=cnt[1]+2*cnt[2];//能分出1的数量
		if(s>=cnt[3]) {//1多
			if(cnt[1]>=cnt[3]) ans=cnt[1]+cnt[2];//单牌就够,对子直接出
			else {
				ans+=cnt[1];
			    cnt[3]-=cnt[1];//先用单牌填
			    ans+=cnt[3];
			    cnt[2]-=cnt[3]/2;//再用对子
			    ans+=cnt[2];//对子直接出
			}
		}
		else {//3多
			cnt[3]-=s;//能匹配的先匹配
			ans=s+cnt[3]/4*3;//4x3的先考虑
			cnt[3]%=4;
			if(cnt[3]%3) ans+=2;//r=1,2时%3都非0
			else if(cnt[3]) ans+=3;//为0时可能就是0
		}
		printf("%lld\n",ans);
		//华丽输出
	}
	return 0;
}

后话

请切记:一拍脑子出的结论一定要验算一下(如本蒟蒻先丢完炸弹的愚蠢想法),不然拿着错结论代码写出花来都过不了。

如果有用还请点个赞!

审核求过啊!

posted @ 2024-11-26 14:18  Tenil  阅读(40)  评论(0)    收藏  举报