2015

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值