Task
D1T3 斗地主
共T组,每组n张牌,大小关系大王>小王>K>*>2>1.
花色不对牌大小产生影响,给定出牌方式,求最小出牌次数。
数据范围:
Solution
30%的数据
n<=4 所有的情况都在“三带二”的前面。
如果cnt最大值<=2,表明每种牌都单独打(单张或对子),此时最优解=权值种类数。
如果cnt>=3,如果存在另一张牌,就可以被带打,只需要1次。
100% dfs 搜索
数据范围小于25,转移状态多,不好判重,考虑暴搜。
如果最优解是确定的,出牌的相对顺序是不重要的。如果每次出更多的牌,就可以减少不必要的分支,提高搜索的效率。
出牌可以分为2大类:顺子,非顺子[①x带y,②单打(1,2,3,4)]
顺子出牌较多,因此如果先按照顺子的顺子暴搜更优。剩余的状态按照先①再②的顺序可以不用dfs,直接贪心处理。
处理出有多少数码有k张牌,尽可能一次带更多的牌。
我的错误:
① 题意理解有误,认为顺子不能带1,2,王。实际上可以带1.
② 在贪心处理非顺子的两类时,4带2张不同的单牌优于带2张相同的单牌。
③ 最优性剪枝时,只有当前值>=ans,才返回,而不是当前值+展望值>=ans返回。因为可能随顺子个数增多,展望值变小,总和变小。
const int M=15;
int cas,n,ans,T=13;
int cnt[M],c[5];
inline void print(){
puts("cnt");
rep(i,0,T)
if(cnt[i])printf("%d %d\n",i,cnt[i]);
puts("");
}
inline int cal(){//"带 " "单"
memset(c,0,sizeof(c));
rep(i,0,T)c[cnt[i]]++;
int ans=0;
while(c[4]&&c[2]>=2){c[4]--;c[2]-=2;ans++;}
while(c[4]&&c[1]>=2){c[4]--;c[1]-=2;ans++;}
while(c[4]&&c[2]){c[4]--;c[2]--;ans++;}
while(c[3]&&c[2]){c[3]--;c[2]--;ans++;}
while(c[3]&&c[1]){c[3]--;c[1]--;ans++;}
return ans+c[1]+c[2]+c[3]+c[4];
}
inline bool Over(int l,int r,int a){
rep(i,l,r)if(cnt[i]<a)return 0;
return 1;
}
inline void Add(int l,int r,int a){
rep(i,l,r)cnt[i]+=a;
}
inline void dfs(int cur){
if(cur>=ans)return;//最优性剪枝
int num=cal();
MIN(ans,cur+num);
{//3顺
rep(i,2,T)
rep(j,i+1,T){
if(!Over(i,j,3))break;
Add(i,j,-3);
dfs(cur+1);
Add(i,j,3);
}
}
{//2顺
rep(i,2,T)
rep(j,i+2,T){
if(!Over(i,j,2))break;
Add(i,j,-2);
dfs(cur+1);
Add(i,j,2);
}
}
{//1顺
rep(i,2,T)
rep(j,i+4,T){
if(!Over(i,j,1))break;
Add(i,j,-1);
dfs(cur+1);
Add(i,j,1);
}
}
}
int main(){
int a,b;
rd(cas);rd(n);
rep(ii,1,cas){
ans=50;
memset(cnt,0,sizeof(cnt));//多cas清空
rep(i,1,n){
rd(a);rd(b);
if(a==1)a=13;
else if(a>1)a--;
cnt[a]++;
}
dfs(0);
sc(ans);
}
return 0;
}