簡要題意
給你一副手牌,求最少的次數出完所有手牌。(按照它給出的規定出)
題目
分析
因為求最小次數直接貪心很明顯是錯的,但又直接寫不出 \(dp\) 的式子,所以我們只能夠爆搜所有情況,但這樣明顯會超時,只有剪枝,我們記錄了各個數碼的個數,但其實除了順子以外,其他的出牌並不關心數碼的大小,只關心個數。所以我們單獨處理順子,然後只記錄 \(js[i]\) 表示有 \(i\) 張牌的數碼個數,然後在模擬出牌。這個時候我們發現,只要 \(js\) 一樣答案就一樣與先後順序等無關,所以記憶化搜尋(dp)記錄一下就可以了。但一定要注意一些特殊情況,如雙王可以當對子出,不能當對子用,有可能將三個的拆成一個和兩個更優,同理四個拆成一個和三個或兩個兩個。
程式碼
點選檢視程式碼
#include <bits/stdc++.h>
using namespace std;
int t,n,cnt[20],js[10],xz[5]={0,5,3,2};
int dp[30][30][30][30];
int dfs()
{
if(dp[js[1]][js[2]][js[3]][js[4]]!=-1)return dp[js[1]][js[2]][js[3]][js[4]];
int sum=n;
if(js[2])
{
js[2]--;js[1]+=2;
sum=min(sum,dfs());
js[2]++;js[1]-=2;
}//chai2
if(js[3])
{
js[3]--;js[1]+=1,js[2]+=1;
sum=min(sum,dfs());
js[3]++;js[1]-=1,js[2]-=1;
}
if(js[4])
{
js[4]--;js[1]+=1,js[3]+=1;
sum=min(sum,dfs());
js[4]++;js[1]-=1,js[3]-=1;
js[4]--;js[2]+=2;
sum=min(sum,dfs());
js[4]++;js[2]-=2;
}
if(js[1])js[1]--,sum=min(sum,1+dfs()),js[1]++;//danpai
if(js[2])js[2]--,sum=min(sum,1+dfs()),js[2]++;//duizi
if(js[3])js[3]--,sum=min(sum,1+dfs()),js[3]++;//san
if(js[4])js[4]--,sum=min(sum,1+dfs()),js[4]++;//炸彈
if(js[3]&&js[1])
{
js[3]--;js[1]--;
sum=min(sum,1+dfs());
js[3]++;js[1]++;
} //3+1
if(js[3]&&js[2])
{
js[3]--;js[2]--;
sum=min(sum,1+dfs());
js[3]++;js[2]++;
} //3+2
if(js[2]>=2&&js[4])
{
js[4]--;js[2]-=2;
sum=min(sum,1+dfs());
js[4]++;js[2]+=2;
}
if(js[1]>=2&&js[4])
{
js[4]--;js[1]-=2;
sum=min(sum,1+dfs());
js[4]++;js[1]+=2;
}//4+2
return dp[js[1]][js[2]][js[3]][js[4]]=min(sum,js[1]+js[2]+js[3]+js[4]);
}
int shunzi(int step)
{
int ans=n;
for(int k=1;k<=3;k++)
for(int i=1;i<=12-xz[k]+1;i++)
{
int tot=0;
for(int j=i;j<=12;j++)
{
if(cnt[j]>=k)tot++;
else break;
}
for(int j=i+xz[k]-1;j<=i+tot-1;j++)
{
// cout<<i<<" "<<j<<'\n';
for(int l=i;l<=j;l++)js[cnt[l]]--,cnt[l]-=k,js[cnt[l]]++;
ans=min(ans,shunzi(step+1));//可以出多個順子
for(int l=i;l<=j;l++)js[cnt[l]]--,cnt[l]+=k,js[cnt[l]]++;
}
}
ans=min(ans,step+dfs());//出完順子直接跑
if(cnt[14]==2)
{
js[1]-=2;
ans=min(step+1+dfs(),ans);//出個王炸再跑。
js[1]+=2;
}
return ans;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>t>>n;
memset(dp,-1,sizeof(dp));
dp[0][0][0][0]=0;
while(t--)
{
memset(cnt,0,sizeof(cnt));
memset(js,0,sizeof(js));
for(int i=1;i<=n;i++)
{
int ai,bi;cin>>ai>>bi;
if(ai>=1)
{
if(ai>=3)cnt[ai-2]++;
else cnt[ai+11]++;//A,2
}
else cnt[14]++;//wang
}
for(int i=1;i<=14;i++)js[cnt[i]]++;
if(cnt[14]&&cnt[14]==2)js[1]+=2,js[2]-=1;//處理雙王
cout<<shunzi(0)<<'\n';
}
return 0;
}
反思
1.不要省一些極小的記憶體,增加程式碼長度,直接將陣列當形參傳入即可減少一半的長度,不用復原。
2.思考的不全面,一開始沒有想到出完一個順子後再出順子,以及王炸當對子用(如三帶二帶王炸),以及開始寫前沒有充分思考。