做题随笔: 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;
}
后话
请切记:一拍脑子出的结论一定要验算一下(如本蒟蒻先丢完炸弹的愚蠢想法),不然拿着错结论代码写出花来都过不了。
如果有用还请点个赞!
审核求过啊!

浙公网安备 33010602011771号