NOIP2015 鬥地主(回溯)

題目描述

 牛牛最近迷上了一種叫鬥地主的撲克遊戲。鬥地主是一種使用黑桃、紅心、梅花、方片的A到K加上大小王的共54張牌來進行的撲克牌遊戲。在鬥地主中,牌的大小關係根據牌的數碼錶示如下:3<4<5<6<7<8<9<10<J<Q<K<A<2<小王<大王,而花色並不對牌的大小產生影響。每一局遊戲中,一副手牌由n張牌組成。遊戲者每次可以根據規定的牌型進行出牌,首先打光自己的手牌一方取得遊戲的勝利。現在,牛牛隻想知道,對於自己的若干組手牌,分別最少需要多少次出牌可以將它們打光。請你幫他解決這個問題。需要注意的是,本題中游戲者每次可以出手的牌型與一般的鬥地主相似而略有不同。具體規則如下:

""

輸入

第一行包含用空格隔開的2個正整數T,N,表示手牌的組數以及每組手牌的張數。

接下來T組數據,每組數據N行,每行一個非負整數對Ai,Bi,表示一張牌,其中Ai表示牌的數碼,Bi表示牌的花色,中間用空格隔開。特別的,我們用1來表示數碼A,11表示數碼J,12表示數碼Q,13表示數碼K;黑桃、紅心、梅花、方片分別用1-4來表示;小王的表示方法爲01,大王的表示方法爲02。

輸出

共T行,每行一個整數,表示打光第T組手牌的最少次數。

樣例輸入

1 8
7 4
8 4
9 1
10 4
11 1
5 1
1 4
1 1

樣例輸出

3

比較噁心的回溯...
花色無用,大小王可以當成兩張相同的牌,只能單出或當對子.
首先注意順子比帶牌優,四帶兩對要比四帶兩單優,三帶一對比三帶一單優,判斷時注意先後順序.
過程分析:dfs模擬出牌,每次dfs首先統計如果當前出完需要多少步,更新ans;
solve函數:模擬帶牌過程,統計如果將牌全部出完所需步數來更新ans;
dfs:模擬出順子的過程。注意順子不一定是最長就最優,比如當前的最長的順子可能是在拆開另一對順子或帶牌時得到的,所以要枚舉順子的長度來保證回溯到所有結果,注意二點大小王不能用.
主體就這兩部分,主要是順子和帶牌的處理,或許描述的不夠清楚,具體細節參考代碼註釋.

#include<cstdio>
#include<iostream>
#include<cstring>
#define maxn 16
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
inline int read()
{   char c=getchar();int x=0,y=1;
    while(c<'0'||c>'9'){if(c=='-') y=-1;c=getchar();}
    while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x*y;
}
int t,n,ans,num[maxn],cnt,sz[maxn];
inline int st(int x)//爲了後面計算方便,將3標爲一號,1,2分別標爲12,13號,大小王當成一張14號 
{	if(x>=3) return x-2;
	if(x==1) return 12;
	if(x==2) return 13;
	if(x==0) return 14; 
}
int solve()//處理帶牌 
{	int tmp=0;mem(sz,0);//注意清空數組 
	for(int i=1;i<=14;i++) sz[num[i]]++;//統計一定數量的牌有多少張,舉個例子,如果整副牌裏只有兩個對子JJ,QQ,那麼sz[2]=2; 
	while(sz[4]&&sz[2]>=2) tmp++,sz[4]--,sz[2]-=2;//模擬四帶兩對的過程 
	while(sz[4]&&sz[1]>=2) tmp++,sz[4]--,sz[1]-=2;//模擬四帶兩單的過程 
	while(sz[3]&&sz[2]>=1) tmp++,sz[3]--,sz[2]--;//模擬三帶一對的過程 
	while(sz[3]&&sz[1]>=1) tmp++,sz[3]--,sz[1]--;//模擬三帶一單的過程 
	return tmp+sz[1]+sz[2]+sz[3]+sz[4];//剩下的對子和單牌依次出,所以tmp+sz[1]+sz[2]+sz[3]+sz[4]就是出完牌所需的步數 
}
void dfs()
{	if(cnt>ans) return;
	ans=min(ans,cnt+solve());//更新ans,cnt+返回值就是最終結果 
	for(int i=1;i<=11;i++)//模擬3順子,注意邊界 
	{	int j=i;
		while(num[j]>=3&&j<=12) j++;//找到最長的順子 
		if(j-i<2) continue;//長度必須滿足條件 
		for(int k=j;k-i>=2;k--)//枚舉長度,注意從長到短枚舉,長的更優
		{	for(int l=i;l<k;l++) num[l]-=3;//更新牌數,模擬出牌過程 
			cnt++;dfs();cnt--; 
			for(int l=i;l<k;l++) num[l]+=3;//回溯,恢復之前狀態,注意cnt的恢復 
		}
	}
	for(int i=1;i<=10;i++)//模擬雙順子 
	{	int j=i;
		while(num[j]>=2&&j<=12) j++;
		if(j-i<3) continue;
		for(int k=j;k-i>=3;k--)
		{	for(int l=i;l<k;l++) num[l]-=2;
			cnt++;dfs();cnt--;
			for(int l=i;l<k;l++) num[l]+=2;
		}
	}
	for(int i=1;i<=8;i++)//模擬單順子 
	{	int j=i;
		while(num[j]>=1&&j<=12) j++;
		if(j-i<5) continue;
		for(int k=j;k-i>=5;k--)
		{	for(int l=i;l<k;l++) num[l]-=1;
			cnt++;dfs();cnt--;
			for(int l=i;l<k;l++) num[l]+=1;
		}
	}
}
void init(){mem(num,0);ans=0x7fffffff;cnt=0;}
int Anonymity()
{	//freopen("landlords.in","r",stdin);
	//freopen("landlords.out","w",stdout);
	t=read();n=read();
	while(t--)
	{	int tmp,tmp2;init();//初始化 
		for(int i=1;i<=n;i++) tmp=read(),tmp2=read(),num[st(tmp)]++;
		dfs();printf("%d\n",ans);
	}
	return 0;
}
int sakural=Anonymity();//兩個id ^.^ 
int main(){;}








發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章