把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷5370】[PKUSC2018] 主斗地(暴搜)

点此看题面

  • 给出斗地主的若干种出牌牌型(详见原题面)。
  • 现在有两个队友\(A,B\)。每轮\(A\)出一种牌型,\(B\)出一种同牌型的严格更大的牌,若二人能一起把牌打完则获胜。
  • 从一副牌中分给每人\(17\)张,给定\(B\)的手牌,问\(A\)有多少种可能的手牌使得二人能获胜。(手牌中已除去\(3\)

暴搜所有情况+暴搜验证

由于已经除去了\(3\),所以\(A\)可能的手牌种类是很少的,我们可以直接暴搜出所有情况。

然后考虑验证,我们发现对子、三张牌、顺子、连对、三顺都可以直接拆成单牌,飞机可以直接拆成三带一和三带二。

因此我们实际上只用搜出三带一、三带二、四带二的情况,剩余的单牌排个序之后直接比较。

更进一步,其实我们只用搜出三与三配对、四与四配对各自的数目,验证时枚举三三中有多少个带一,则必然贪心地带走\(A\)中最大的那些,带走\(B\)中最小的那些。

而搜三与三配对和四与四配对的时候有个优化,就是四与四配对肯定选择最近的四,三与三配对可能选择最近的三,但也有可能选择之后任意一个四中的三个。

具体实现详见代码,一开始智障写错不少细节。

代码:\(O(\)能过\()\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
using namespace std;
int s[20],a[20],b[20],c[20];string st;
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	char oc,FI[FS],*FA=FI,*FB=FI;
	Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}using namespace FastIO;
int qa[20],qb[20],ta[20],tb[20];I bool Check(CI o)//贪心检验
{
	RI i,j,k,x,s=0;for(i=1;i<=14;++i) s+=a[i];RI w=(17-s-4*o)/3;if(w+2*o>s) return 0;
	RI t;for(x=0;x<=w;++x)//枚举三带一的个数
	{
		for(i=1;i<=14;++i) ta[i]=a[i],tb[i]=b[i];//复制一份
		for(i=14,j=x+2*o,k=w-x;i>=1;--i) {W(ta[i]>=2&&k) ta[i]-=2,--k;W(ta[i]&&j) --ta[i],--j;}if(j||k) continue;//贪心选大的
		for(i=1,j=x+2*o,k=w-x;i<=14;++i) {W(tb[i]>=2&&k) tb[i]-=2,--k;W(tb[i]&&j) --tb[i],--j;}if(j||k) continue;//贪心选小的
		for(t=0,i=1;i<=14;++i) for(j=1;j<=ta[i];++j) qa[++t]=i;
		for(t=0,i=1;i<=14;++i) for(j=1;j<=tb[i];++j) qb[++t]=i;
		for(i=1;i<=t;++i) if(qa[i]>=qb[i]) break;if(i>t) return 1;//有序,直接比较
	}return 0;
}
I bool C3(RI x,RI y,RI z,CI o)//三与三配对
{
	W(x<=14&&a[x]<3) ++x;W(y<=14&&(y<=x||b[y]^3)) ++y;W(z<=14&&(z<=x||b[z]^4)) ++z;
	if(y>14&&z>14) return Check(o);if(C3(x+1,y,z,o)||(z<=14&&C3(x,y,z+1,o))) return 1;//不配对,或不与这个四配对
	if(y<=14) {a[x]-=3,b[y]-=3;RI t=C3(x+1,y+1,z,o);a[x]+=3,b[y]+=3;if(t) return 1;}//与最近的三配对
	if(z<=14) {a[x]-=3,b[z]-=3;RI t=C3(x+1,y,z+1,o);a[x]+=3,b[z]+=3;if(t) return 1;}return 0;//与某个四配对
}
I bool C4(RI x,RI y,CI o)//四与四配对
{
	W(x<=14&&a[x]^4) ++x;W(y<=14&&(y<=x||b[y]^4)) ++y;if(y>14) return C3(1,1,1,o);//不配对
	if(C4(x+1,y,o)) return 1;a[x]-=4,b[y]-=4;RI t=C4(x+1,y+1,o+1);a[x]+=4,b[y]+=4;return t;//与最近的四配对
}
int ans;I void dfs(CI x,CI t)//暴搜所有情况
{
	if(t==17) return (void)(ans+=C4(1,1,0));if(x>14) return;//牌满之后就去验证
	for(RI i=0;i<=min(c[x]-b[x],17-t);++i) a[x]=i,dfs(x+1,t+i);a[x]=0;//暴搜x选几张
}
int main()
{
	#define V(x) (x=='T'?7:(x=='J'?8:(x=='Q'?9:(x=='K'?10:\
		(x=='A'?11:(x=='2'?12:(x=='w'?13:(x=='W'?14:x-51))))))))//按大小转化为数字
	RI i;for(cin>>st,i=1;i<=14;++i) a[i]=b[i]=0,c[i]=i<=12?4:1;//c记录每种牌的数量
	for(i=1;i<=17;++i) ++b[s[i]=V(st[i-1])];return dfs(1,0),printf("%d\n",ans),0;//记录B每种牌数,暴搜
}
posted @ 2021-05-10 09:29  TheLostWeak  阅读(107)  评论(0编辑  收藏  举报