例78 撲克游戲
問題描述
亞當和夏娃用一副52張的普通牌玩紙牌游戲。規則很簡單,兩人面對面坐在桌子的兩側。每人從牌堆中取出k張牌,看了之后,將牌面朝下放在桌子上。亞當的牌從左邊的1到k編號,夏娃的牌從右邊的1到k編號(所以夏娃的第i張牌與亞當的第i張牌相對)。卡片正面朝上,積分如下:
如果亞當的第i(i∈ {1,…,k})張牌勝過夏娃的第i張牌,那么亞當得1分。
如果夏娃的第i張牌勝過亞當的第i張牌,那么夏娃得一分。
點數較高的牌總是勝過點數較低的牌:3勝2、4勝3、…。1張A牌勝過每1張牌,除了(可能)另1張A牌。
如果兩張牌的點數相同,那么牌的花色就決定了誰贏:紅桃勝過所有其他牌,黑桃勝過除紅桃以外的所有花色,方塊只勝過梅花,梅花不勝過任何一種花色。
例如,紅桃10勝過黑桃10,黑桃10勝過方塊10,方塊10勝過梅花10。
這應該是一場機會游戲,但最近夏娃大部分時間都贏了,原因是她已經開始使用有標記的牌。換言之,她在亞當把牌翻過來之前就知道他桌上有哪些牌。利用這些信息,她可以先排好自己的牌,以便獲得盡可能多的積分。
你的任務是,根據亞當和夏娃的牌,確定夏娃如果打得最好會得到多少分。
輸入
輸入包括多組測試用例。第一行輸入將包含一個正整數N,給出測試用例的數量。
每個測試用例從一行開始,該行帶有一個正整數k<=26,這是每個玩家獲得的牌數。
下一行描述了亞當放在桌子上的k張牌,從左到右。
再下一行描述了夏娃的k張牌(但她還沒有把它們放在桌子上)。卡片由兩個字符描述,第一個是它的點數(2、3、4、5、6、7、8、9、T、J、Q、K或A),第二個是它的花色(C、D、S或H,分別表示梅花、方塊、黑桃或紅桃)。牌之間用空格隔開。因此,如果亞當的牌是梅花10,紅桃2和方塊J,那可以描述為 TC 2H JD。
輸出
對於每個測試用例,如果夏娃選擇了最佳的方式將卡片排列在桌子上,輸出其最多得分。
輸入樣例
3
1
JD
JH
2
5D TC
4C 5H
3
2H 3H 4H
2D 3D 4D
輸出樣例
1
1
2
(1)編程思路。
由於需要對撲克牌進行排序,而采用牌面字符串不方便進行排序,因此可以編寫函數int getVal(char card[5])將52張撲克牌中的某張由card字符串指定的牌面轉換為對應的整數值(8~59之一)。轉換時,牌面2C=8、2D=9、2S=10、2H=11、…、AC=56、AD=57、AS=58、AH=59。
撲克牌的一張牌面包含點數和花色兩個信息,可以將點數2~9分別取2~9,T、J、Q、K分別取10、11、12、13,A取14,四種花色C、D、S、H分別取0、1、2、3,這樣每張牌按牌面可映射為一個整數值,映射公式為: 牌面值=點數*4+花色。
這樣,對撲克牌的排序就可以轉換為對整數的排序。
輸入亞當和夏娃各自所取的K張牌后,將每張牌按牌面轉換為一個整數值並分別保存到數組A和數組E中,然后將數組A和E分別按從小到大的順序排列好。
為了求出夏娃的最多得分,可以采用貪心法求解。
貪心策略是:如果夏娃當前最大的牌可以贏亞當最大的牌,那么讓這兩張牌比大小,贏得1分;如果夏娃當前最小的牌能贏亞當最小的牌,那么讓這兩牌比大小,贏得1分;如果上面兩個條件都不滿足,就讓夏娃當前最小的牌和亞當當前最大的牌比大小,讓亞當得1分,但用掉其最大的牌。
(2)源程序。
#include <stdio.h> void sort(int a[],int n) { int i,j,tmp; for (i=0;i<n-1;i++) for (j=0;j<n-1-i;j++) if (a[j]>a[j+1]) { tmp=a[j]; a[j]=a[j+1]; a[j+1]=tmp; } } int value(char card[]) { int p,s; if (card[0]>='2' && card[0]<='9') p=card[0]-'0'; else if (card[0]=='T') p=10; else if (card[0]=='J') p=11; else if (card[0]=='Q') p=12; else if (card[0]=='K') p=13; else if (card[0]=='A') p=14; if (card[1]=='C') s=0; else if (card[1]=='D') s=1; else if (card[1]=='S') s=2; else if (card[1]=='H') s=3; return 4*p+s; } int main() { int n; scanf("%d",&n); while (n--) { int a[27],e[27]; int k; scanf("%d",&k); char card[3]; int i; for (i=0;i<k;i++) { scanf("%s",card); a[i]=value(card); } for (i=0;i<k;i++) { scanf("%s",card); e[i]=value(card); } sort(a,k); sort(e,k); int cnt=0; int aBegin=0,aEnd=k-1; int eBegin=0,eEnd=k-1; while (eBegin<=eEnd) { if (e[eBegin]>a[aBegin]) { aBegin++; eBegin++; cnt++; } else if (e[eEnd]>a[aEnd]) { aEnd--; eEnd--; cnt++; } else { eBegin++; aEnd--; } } printf("%d\n",cnt); } return 0; }
習題78
78-1 紙牌游戲
問題描述
Alice和她的朋友Bob正在玩一種紙牌游戲。這個游戲里要用到一副 2N張牌的套牌,編號從1到2N。Alice和Bob每個人各分得N張卡牌。接下來進行N輪比賽,Alice和Bob每輪各出一張牌。每一輪誰的牌編號更大,誰就贏得了本輪的勝利。
Alice已經預測了Bob的出牌順序,請幫助Alice算出她最多能贏多少輪。
輸入
第一行一個整數 N(1≤N≤5×104 )。
接下來N行,第i行一個整數,表示Bob第i輪出的牌。注意Alice手中的N張牌很容易從輸入中推出。
輸出
輸出Alice最多能贏多少輪。
輸入樣例
3
1
6
4
輸出樣例
2
說明/提示
Alice手中拿着 2,3,5三張牌。
她第一輪出2,第二輪出3,第三輪出5,從而贏得一,三兩輪。可以證明不存在更優的方案。
(1)編程思路。
定義數組int h[100005];初始值全部為0。依次輸入Bob的N張卡牌的編號,對於每個編號x,置h[x]=1。
定義數組int a[50001],b[50001]分別保存Alice和Bob手里的卡牌,輸入結束后,對數組h進行掃描,將h[i]=0的下標i依次保存到數組a中,這是Alice所取的卡牌;將h[i]=1的下標i依次保存到數組b中,這是Bob所取的卡牌。顯然,數組a和數組b均已按卡牌的編號從小到大排列好了。
為了讓Alice贏最多的輪次,可以采用貪心法求解。
貪心策略是:如果Alice當前編號最大的牌可以贏Bob當前編號最大的牌,那么讓這兩張牌比大小,Alice贏1輪;如果Alice當前編號最小的牌能贏Bob當前編號最小的牌,那么讓這兩牌比大小,Alice贏1輪;如果上面兩個條件都不滿足,就讓Alice當前編號最小的牌和Bob當前編號最大的牌比大小,讓Bob贏這1輪,但這輪用掉了Bob編號最大的牌。
(2)源程序。
#include <stdio.h> int main() { int h[100005]={0},a[50001],b[50001]; int n; scanf("%d",&n); int i; for (i=1;i<=n;i++) { int x; scanf("%d",&x); h[x]=1; } int ak=1,bk=1; for (i=1;i<=2*n;i++) { if (h[i]==0) a[ak++]=i; else b[bk++]=i; } int cnt=0; int aBegin=1,aEnd=n; int bBegin=1,bEnd=n; while (aBegin<=aEnd) { if (a[aBegin]>b[bBegin]) { aBegin++; bBegin++; cnt++; } else if (a[aEnd]>b[bEnd]) { aEnd--; bEnd--; cnt++; } else { aBegin++; bEnd--; } } printf("%d\n",cnt); return 0; }
78-2 嚴格遞增的序列
問題描述
給定一個長度為n的序列,你可以將序列中某些元素各減去一個數,使得整個序列嚴格遞增。
你需要求出所有減去的數的總和的最小值。
例如:有一個長度為3的序列 5,5,5;最優方案是 5-2,5-1,5即 3,4,5。這樣所有減去的數的總和是 2+1=3,為最小值。
輸入
輸入第一行一個整數 n(1≤n≤100),表示序列的長度。
第二行n個整數,描述這個序列。
輸出
輸出一行一個整數,表示總和的最小值。
輸入樣例
4
5
3
7
5
輸出樣例
6
(1)編程思路。
要是序列嚴格遞增,即序列的下一個數必須至少比前1個數大1,為了使所有減去的數的總和值最小,因此,貪心策略為:如果前1個數不小於后1個數,則把前1數減至比后1數小1即可。
具體處理時,從第n-1個數開始倒推處理到第1個數,即最后的第n個數不變。
若第i(i從n-1循環處理到1)個數比第i+1個數大(或相等),讓第i個數等於第i+1個數-1,並累計它們的差值。
(2)源程序。
#include <stdio.h> int main() { int n; scanf("%d",&n); int a[105]; int i; for (i=1;i<=n;i++) scanf("%d",&a[i]); int ans=0; for (i=n-1;i>=1;i--) if (a[i]>=a[i+1]) { ans+=a[i]-a[i+1]+1; a[i]=a[i+1]-1; } printf("%d\n",ans); return 0; }
78-3 卡牌游戲
問題描述
小明某天想到了一個卡牌游戲,游戲規則如下:
初始時小明的手中有自左向右排成一排的n 張卡牌,每張卡牌上有一個整數分值。
接下來,小明每次可以選取卡牌序列最左邊的連續若干張卡牌(至少2張),將它們替換為一張新卡牌。新卡牌將插入到序列的最左端,它的分值為本次操作中被替換掉的卡牌的分值之和。
初始時小明總分為0,每執行一次卡牌替換操作,新卡牌的分值將加到總分中。當序列長度為1時游戲結束,小明也可以在任意時刻結束游戲。
現在給出序列中各個卡牌的分值,請你來幫助小明計算他能夠獲得的最高總分是多少?
輸入
第一行一個正整數n,代表卡牌的數目。
接下來一行n個以空格分隔的整數,第 i個數字ai代表自左向右第i張卡牌的分值。
(1≤n≤105,ai≤105)
輸出
僅一行一個整數表示答案。
輸入樣例
7
-4 3 0 7 -3 -5 -3
輸出樣例
9
樣例解釋
最優策略為,首先選擇最左側的四張卡牌,總分增加 (-4) + 3 + 0 + 7 = 6。此時小明選擇的四張卡牌被替換為一張分值為6 的卡牌,且被放入序列最左側,此時自左向右卡牌的分值為 6, -3, -5, -3。
再選擇最左側的兩張卡牌,總分增加 6 + (-3) = 3,總分為9。此時小明選擇的兩張卡牌被替換為一張分值為3的卡牌,且被放入序列最左側,此時自左向右卡牌的分值為 3, -5, -3。
此時無論如何操作均無法使總分繼續增大,小明選擇結束游戲。
(1)編程思路。
定義變量sum來保存輸入的卡牌分值的前綴和,初始化sum=第1張牌的分值,定義長整型變量ans保存能夠獲得的最高總分,初始值為0。
用循環從第2張卡牌開始依次讀入第i張卡牌的分值x,並累加到sum上去,若sum>0,則將sum再累加到ans上,表示從第1張牌到第i張牌替換為一張分值為sum的新卡牌;若sum<=0,則不能增大總分值,因此不替換。
(2)源程序。
#include <stdio.h> int main() { int n; scanf("%d",&n); long long x,sum; scanf("%lld",&x); sum=x; long long ans=0; int i; for (i=2;i<=n;i++) { scanf("%lld",&x); sum+=x; if (sum>0) ans+=sum; } printf("%lld\n",ans); return 0; }