15 年的 NOIP 提高组试题,被搬到今天校模拟赛,只糊了 20 分,结果人均 50 分贪心……
这张图片可以作为题目描述:
并不是跟别人斗地主,而是要求尽快出完牌,输出步数。 \(n \le 20\),多组数据。
对于 \(n \le 5\) 的那些测试点,当然可以手工模拟。更进一步,根据打牌的经验(显然我就没有),不难产生一些贪心思路,例如先出顺子、尽可能四带二、三带二、三带一等。
稍加考虑,一般的贪心都是错误的——只能采取搜索了。然而搜索的时间复杂度很高,O(TLE),所以需要剪枝。
贪心地搜索。每一次出牌是一个阶段,分为顺子、双顺子、三顺子、四带二、三带二、三带一 6 种出牌方式,向下递归,回溯;
最优化剪枝。如果当前出牌次数大于等于当前答案,直接返回;
不在“一个三、一个五”这样的零散牌上费时间,在函数体最后扫一遍剩下的牌,步数++即可。
代码实现上有很多细节,例如大小王的判断(出对子时永远 13 种牌,单出才有 14 种)或 A 的判断(它可以作为对子或顺子的一部分)。下附 AC 代码。
#include<bits/stdc++.h> using namespace std; inline int rd(){ int x=0,f=1;char c=getchar(); while(!isdigit(c)){if(c=='-') f^=1;c=getchar();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} return f?x:-x; } int T,n,ans,a[30]; void dfs(int d){ if(d>=ans) return; //1. 顺子(5) for(int l=3;l<=10;++l){ if(!a[l]) continue; int r=l+1; while(r<=13&&a[r]) ++r;--r; if(r==13&&a[1]) ++r; //最长[l,r] if(r-l+1<5) continue; for(int i=l+4;i<=r;++i){ for(int j=l;j<=i;++j) if(j<=13) --a[j]; else --a[1]; dfs(d+1); for(int j=l;j<=i;++j) if(j<=13) ++a[j]; else ++a[1]; } } //2. 双顺子(3) for(int l=3;l<=12;++l){ if(a[l]<2) continue; int r=l+1; while(r<=13&&a[r]>=2) ++r;--r; if(r==13&&a[1]>=2) ++r; if(r-l+1<3) continue; for(int i=l+2;i<=r;++i){ for(int j=l;j<=i;++j) if(j<=13) a[j]-=2; else a[1]-=2; dfs(d+1); for(int j=l;j<=i;++j) if(j<=13) a[j]+=2; else a[1]+=2; } } //3. 三顺子(2) for(int l=3;l<=13;++l){ if(a[l]<3) continue; int r=l+1; while(r<=13&&a[r]>=3) ++r;--r; if(r==13&&a[1]>=3) ++r; if(r-l+1<2) continue; for(int i=l+1;i<=r;++i){ for(int j=l;j<=i;++j) if(j<=13) a[j]-=3; else a[1]-=3; dfs(d+1); for(int j=l;j<=i;++j) if(j<=13) a[j]+=3; else a[1]+=3; } } //4. 四带二 for(int i=1;i<=13;++i){ if(a[i]<4) continue; a[i]=0; //4.1 带两张零牌 / 一个对子 for(int j=1;j<=14;++j){ if(!a[j]) continue; --a[j]; for(int k=1;k<=14;++k){ if(!a[k]) continue; --a[k],dfs(d+1),++a[k]; } ++a[j]; } //4.2 带两个对子 for(int j=1;j<=13;++j){ if(a[j]<2) continue; a[j]-=2; for(int k=1;k<=13;++k){ if(a[k]<2) continue; a[k]-=2,dfs(d+1),a[k]+=2; } a[j]+=2; } a[i]=4; } //5. 三带二 / 三带一 for(int i=1;i<=13;++i){ if(a[i]<3) continue; a[i]-=3; for(int j=1;j<=13;++j){ if(a[j]<2||i==j) continue; a[j]-=2,dfs(d+1),a[j]+=2; } for(int j=1;j<=14;++j){ if(!a[j]||i==j) continue; --a[j],dfs(d+1),++a[j]; } a[i]+=3; } //6. 零牌 for(int i=1;i<=14;++i) if(a[i]) ++d; ans=min(ans,d); } int main(){ T=rd(),n=rd(); while(T--){ ans=0x7fffffff; memset(a,0,sizeof(a)); for(int i=1,x,y;i<=n;++i){ x=rd(),y=rd(); x?++a[x]:++a[14]; } dfs(0); printf("%d\n",ans); } return 0; }