题意

NOIP 2016 斗地主

给你一些牌,按照斗地主的出牌方式,问最少多少次出完所有的牌。

分析

这道题的做法是DFS。

为了体现这道题的锻炼效果,我自己写了好多个代码。

Ver1

直接暴力搞,加深迭代搜索。

发现出牌顺序不影响解决。

优化:先搞顺子,再搞N带N,再搞火箭,再搞N张

int DFS(int nd,int lim)
{
    int cur;
    int siz;

    if (nd>lim)
        return 0;

    siz=0;
    rep(i,3,17)
        siz+=cnt[i];
    if (nd==lim&&!siz)
        return 1;

    rep(i,3,17) if (cnt[i]>=4)
    {
        cnt[i]-=4;
        rep(j,3,17) if (cnt[j]>=1)
        {
            cnt[j]--;
            rep(k,3,17) if (cnt[k]>=1)
            {
                cnt[k]--;
                if (DFS(nd+1,lim))
                    return 1;
                cnt[k]++;
            }
            cnt[j]++;
        }
        cnt[i]+=4;
    }

    rep(i,3,14) if (cnt[i]>=3)
    {
        cur=i;
        while (cur+1<=14&&cnt[cur+1]>=3)
            cur++;
        rep(j,2,cur-i+1)
        {
            rep(k,i,i+j-1) cnt[k]-=3;
            if (DFS(nd+1,lim))
                return 1;
            rep(k,i,i+j-1) cnt[k]+=3;
        }
    }

    rep(i,3,14) if (cnt[i]>=2)
    {
        cur=i;
        while (cur+1<=14&&cnt[cur+1]>=2)
            cur++;
        rep(j,3,cur-i+1)
        {
            rep(k,i,i+j-1) cnt[k]-=2;
            if (DFS(nd+1,lim))
                return 1;
            rep(k,i,i+j-1) cnt[k]+=2;
        }
    }

    rep(i,3,14) if (cnt[i]>=1)
    {
        cur=i;
        while (cur+1<=14&&cnt[cur+1]>=1)
            cur++;
        rep(j,5,cur-i+1)
        {
            rep(k,i,i+j-1) cnt[k]--;
            if (DFS(nd+1,lim))
                return 1;
            rep(k,i,i+j-1) cnt[k]++;
        }
    }

    rep(i,3,17) if (cnt[i]>=3)
    {
        cnt[i]-=3;
        rep(j,3,17) if (cnt[j]>=2)
        {
            cnt[j]-=2;
            if (DFS(nd+1,lim))
                return 1;
            cnt[j]+=2;
        }
        cnt[i]+=3;
    }

    rep(i,3,17) if (cnt[i]>=3)
    {
        cnt[i]-=3;
        rep(j,3,17) if (cnt[j]>=1)
        {
            cnt[j]--;
            if (DFS(nd+1,lim))
                return 1;
            cnt[j]++;
        }
        cnt[i]+=3;
    }

    rep(i,3,17) if (cnt[i]>=4)
    {
        cnt[i]-=4;
        if (DFS(nd+1,lim))
            return 1;
        cnt[i]+=4;
    }

    rep(i,3,17) if (cnt[i]>=3)
    {
        cnt[i]-=3;
        if (DFS(nd+1,lim))
            return 1;
        cnt[i]+=3;
    }

    if (cnt[16]>=1&&cnt[17]>=1)
    {
        cnt[16]--,cnt[17]--;
        if (DFS(nd+1,lim))
            return 1;
        cnt[16]++,cnt[17]++;
    }

    rep(i,3,17) if (cnt[i]>=2)
    {
        cnt[i]-=2;
        if (DFS(nd+1,lim))
            return 1;
        cnt[i]+=2;
    }

    rep(i,3,17) if (cnt[i]>=1)
    {
        cnt[i]--;
        if (DFS(nd+1,lim))
            return 1;
        cnt[i]++;
    }

    return 0;
}

Ver2

在Ver1的基础上:

【优化1】

对于顺子的,原本的操作复杂度为15*15*15:

    rep(i,3,14) if (cnt[i]>=1)
    {
        cur=i;
        while (cur+1<=14&&cnt[cur+1]>=1)
            cur++;
        rep(j,5,cur-i+1)
        {
            rep(k,i,i+j-1) cnt[k]--;
            if (DFS(nd+1,lim))
                return 1;
            rep(k,i,i+j-1) cnt[k]++;
        }
    }

发现每次的操作都是重复的,所以改成15*15的操作:

    rep(i,3,14) if (cnt[i]>=1)
    {
        cur=i;
        while (cur+1<=14&&cnt[cur+1]>=1)
            cur++;
        if (cur-i+1>=5)
        {
            rep(j,i,cur) cnt[j]--;
            per(j,cur,i+4)
            {
                if (j!=cur) cnt[j+1]++;
                if (DFS(nd+1,lim))
                    return 1;
            }
            rep(j,i,i+4) cnt[j]++;
        }
    }

Ver3

改成深搜+剪枝,加一个dwl属性。

枚举完所有的第一种才能枚举第二种,枚举完所有的第二种才能枚举第三种,etc。

void DFS(int nd,int dwl)
{
    int cur;
    int siz;

    if (nd>=ans)
        return;

    siz=0;
    rep(i,3,17)
        siz+=cnt[i];
    if (!siz)
    {
        ans=nd;
        return;
    }

     //①先per优化一个*5
     //②改变顺序
    if (dwl<=1)
    {
        if (cnt[16]>=1&&cnt[17]>=1)
        {
            cnt[16]--,cnt[17]--;
            DFS(nd+1,1);
            cnt[16]++,cnt[17]++;
        }
    }
    //火箭 

    if (dwl<=2)
    {
        rep(i,3,14) if (cnt[i]>=1)
        {
            cur=i;
            while (cur+1<=14&&cnt[cur+1]>=1)
                cur++;
            if (cur-i+1>=5)
            {
                rep(j,i,cur) cnt[j]--;
                per(j,cur,i+4)
                {
                    if (j!=cur) cnt[j+1]++;
                    DFS(nd+1,2);
                }
                rep(j,i,i+4) cnt[j]++;
            }
        }
    }
    //单顺子 

    if (dwl<=3)
    {
        rep(i,3,14) if (cnt[i]>=3)
        {
            cur=i;
            while (cur+1<=14&&cnt[cur+1]>=3)
                cur++;
            if (cur-i+1>=2)
            {
                rep(j,i,cur) cnt[j]-=3;
                per(j,cur,i+1)
                {
                    if (j!=cur) cnt[j+1]+=3;
                    DFS(nd+1,3);
                }
                rep(j,i,i+1) cnt[j]+=3;
            }
        }
    }
    //三顺子 

    if (dwl<=4)
    {
        rep(i,3,14) if (cnt[i]>=2)
        {
            cur=i;
            while (cur+1<=14&&cnt[cur+1]>=2)
                cur++;
            if (cur-i+1>=3)
            {
                rep(j,i,cur) cnt[j]-=2;
                per(j,cur,i+2)
                {
                    if (j!=cur) cnt[j+1]+=2;
                    DFS(nd+1,4);
                }
                rep(j,i,i+2) cnt[j]+=2;
            }
        }
    }
    //双顺子 

    if (dwl<=5)
    {
        rep(i,3,17) if (cnt[i]>=4)
        {
            cnt[i]-=4;
            rep(j,3,17) if (cnt[j]>=1)
            {
                cnt[j]--;
                rep(k,j,17) if (cnt[k]>=1)
                {
                    cnt[k]--;
                    DFS(nd+1,5);
                    cnt[k]++;
                }
                cnt[j]++;
            }
            cnt[i]+=4;
        }
    }
    //四带二

    if (dwl<=6)
    {
        rep(i,3,17) if (cnt[i]>=3)
        {
            cnt[i]-=3;
            rep(j,3,17) if (cnt[j]>=2)
            {
                cnt[j]-=2;
                DFS(nd+1,6);
                cnt[j]+=2;
            }
            cnt[i]+=3;
        }
    }
    //三带二 

    if (dwl<=7)
    {
        rep(i,3,17) if (cnt[i]>=3)
        {
            cnt[i]-=3;
            rep(j,3,17) if (cnt[j]>=1)
            {
                cnt[j]--;
                DFS(nd+1,7);
                cnt[j]++;
            }
            cnt[i]+=3;
        }
    }
    //三带一 

    if (dwl<=8)
    {
        rep(i,3,17) if (cnt[i]>=4)
        {
            cnt[i]-=4;
            DFS(nd+1,8);
            cnt[i]+=4;
        }
    }
    //炸弹 

    if (dwl<=9)
    {
        rep(i,3,17) if (cnt[i]>=3)
        {
            cnt[i]-=3;
            DFS(nd+1,9);
            cnt[i]+=3;
        }
    }
    //三张牌 

    if (dwl<=10)
    {
        rep(i,3,17) if (cnt[i]>=2)
        {
            cnt[i]-=2;
            DFS(nd+1,10);
            cnt[i]+=2;
        }
    }
    //对牌 

    if (dwl<=11)
    {
        rep(i,3,17) if (cnt[i]>=1)
        {
            cnt[i]--;
            DFS(nd+1,11);
            cnt[i]++;
        }
    }
    //单牌
}

Ver4

忘记自己干了啥了。。

Ver5

始终是30ptTLE掉。

所以直接看正解了。

http://blog.csdn.net/xieguofu2014/article/details/50193545

既然有正解了,这里就不再写了。

关键在于把操作进行一下分类:顺子、N带N,然后归纳通性。

附网上的STD(含自己乱七八糟的注释):

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<ctime>

typedef long long ll;
typedef double lf;

const int INF=1<<30;

inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
    return x*f;
}

int T,n,ans;
struct state{
    int num[16];
    inline int& operator[](int x){return num[x];}
}now;
int temp[10];

void dfs(int);
void group(int c,int s,int depth)
//个数为c,至少要有s个,当前已经进行了depth次
{
    for(int st=1,fi;st<=12-s+1;st++){
        for(fi=st;now[fi]>=c&&fi<=12;fi++){
            now[fi]-=c;
            if(fi-st+1>=s)
                dfs(depth+1);
        }
        for(int i=st;i<fi;i++)
            now[i]+=c;
    }
    //暴力剪
}

void dfs(int depth){
    if(depth>ans)return;
    //剪!! 

    int sum=0;
    for(int i=1;i<=15;i++)
        temp[now[i]]++;
    //temp[i]: now[j]=i的个数 

    while(temp[4]>0){
        temp[4]--,sum++;
        //4张直接出
        if(temp[2]>=2)
            temp[2]-=2;
        else if(temp[1]>=2)
            temp[1]-=2;
    }
    //四带二 

    while(temp[3]>0){
        temp[3]--,sum++;
        if(temp[2]>=1)
            temp[2]--;
        else if(temp[1]>=1)
            temp[1]--;
    }
    //三带 

    sum+=temp[1]+temp[2];
    //一和二直接 

    if(temp[1]>=2&&now[14]>=1&&now[15]>=1)
        sum--;
    //处理JOKER的情况:sum-- 

    temp[1]=temp[2]=0;
    if(depth+sum<ans)
        ans=depth+sum;
    //若depth+sum<ans那么就更新

    //上面一大段,解决的是:昨晚所有的顺子之后,直接贪心求解的情况 

    group(1,5,depth);
    //枚举单张,长度为5的单顺子
    group(2,3,depth);
    //枚举双张,长度为3的双顺子
    group(3,2,depth);
    //枚举3张,长度为2的三顺子
}

void work(){
    memset(now.num,0,sizeof now.num);
    ans=1<<30;
    for(int i=1;i<=n;i++){
        int k=read(),c=read();
        if(k==0)
            now[13+c]++;
        else if(k<=2)
            now[11+k]++;
        else
            now[k-2]++;
    }
    dfs(0);
    printf("%d\n",ans);
}

int main(){
    freopen("a.in","r",stdin);
    freopen("a.ans","w",stdout);
    T=read(),n=read();
    for(int turn=1;turn<=T;turn++)
        work();
    return 0;
}

附自己的AC代码:

#include <cstdio>
#include <cstring>
#include <cctype>
#include <climits>
#include <algorithm>
using namespace std;

#define rep(i,a,b) for (int i=(a);i<=(b);i++)
#define per(i,a,b) for (int i=(a);i>=(b);i--)

const int MAX=INT_MAX;

int cas;

int n;
int cnt[16];

int ans;
int cal[5];

inline int rd(void)
{
    int x=0,f=1; char c=getchar();
    for (;!isdigit(c);c=getchar()) if (c=='-') f=-1;
    for (;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}

int Calc(void)
{
    int sum=0;

    memset(cal,0,sizeof cal);
    rep(i,1,15)
        cal[cnt[i]]++;

    while (cal[4]>0)
    {
        sum++; cal[4]--;
        if (cal[2]>=2)
            cal[2]-=2;
        else if (cal[1]>=2)
            cal[1]-=2;
    }

    while (cal[3]>0)
    {
        sum++; cal[3]--;
        if (cal[2]>=1)
            cal[2]--;
        else if (cal[1]>=1)
            cal[1]--;
    }

    sum+=(cal[2]+cal[1]);

    if (cal[1]>=2&&cnt[14]>=1&&cnt[15]>=1)
        sum--;

    return sum;
}

void DFS(int);
void Solve(int c,int len,int dep)
{
    int cur;
    rep(i,1,12-len+1) if (cnt[i]>=c)
    {
        cur=i;
        while (cur+1<=12&&cnt[cur+1]>=c)
            cur++;
        if (cur-i+1>=len)
        {
            rep(j,i,cur) cnt[j]-=c;
            per(j,cur,i+len-1)
            {
                if (j!=cur) cnt[j+1]+=c;
                DFS(dep+1);
            }
            rep(j,i,i+len-1) cnt[j]+=c;
        }
    }
}

void DFS(int dep)
{
    if (dep>ans)
        return;

    int sum=Calc();
    ans=min(ans,sum+dep);

    Solve(1,5,dep);
    Solve(2,3,dep);
    Solve(3,2,dep);
}

int main(void)
{
//  freopen("a.in","r",stdin);
//  freopen("a.out","w",stdout);

    cas=rd(),n=rd();
    rep(tur,1,cas)
    {
        int x,y; memset(cnt,0,sizeof cnt);
        rep(i,1,n)
        {
            x=rd(),y=rd();
            if (0<x&&x<=2) cnt[x+11]++;
            else if (x>2) cnt[x-2]++;
            else cnt[y+13]++;
        }

        ans=MAX;
        DFS(0);
        printf("%d\n",ans);
    }

    return 0;
}

小结

(1)对于操作很多很繁杂的问题时,分类的作用必不可少。

分类的目的在于:限定具有相同结构的一些对象,研究它们的通性,更好地优化问题。

本题就是按照顺子、N带N这两个来分类,先处理完所有的顺子,N带N的就可以贪心了。

其实这种东西在化学课上才刚讲完,为什么想不出来,真的应该好好反思一下。

(2)当操作顺序不影响的时候,我们的想法是定序:规定枚举顺序。即:必然从前面枚举到后面,减少一个数量级。

其实最后的代码并没有使用定序的优化,但是优化后效果更佳。

【NOIP 2016】斗地主的更多相关文章

  1. NOIp 2016 总结

    NOIp 2016 总结 -----YJSheep Day 0 对于考前的前一天,晚自习在复习图论的最短路和生成树,加深了图的理解.睡得比较早,养足精力明日再战. Day 1 拿到题目,先过一边,题目 ...

  2. [NOIP]2016天天爱跑步

    [NOIP]2016天天爱跑步 标签: LCA 树上差分 NOIP Description 小C同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是 ...

  3. Luogu 2668 NOIP 2015 斗地主(搜索,动态规划)

    Luogu 2668 NOIP 2015 斗地主(搜索,动态规划) Description 牛牛最近迷上了一种叫斗地主的扑克游戏.斗地主是一种使用黑桃.红心.梅花.方片的A到K加上大小王的共54张牌来 ...

  4. NOIP 2016 迟来的满贯

    17-03-22,雨 17-03-22,一个特别重要的日子 在这一天,本蒻攻克了NOIP 2016最难的一题,D1T2——天天爱跑步 实现了NOIP 2016的AK! YAYAYAYAYAYAY 自然 ...

  5. NOIP 2016 D2T2 蚯蚓](思维)

    NOIP 2016 D2T2 蚯蚓 题目大意 本题中,我们将用符号 \(\lfloor c \rfloor⌊c⌋\) 表示对 \(c\) 向下取整,例如:\(\lfloor 3.0 \rfloor = ...

  6. noip 2016 提高组题解

    前几天写的那个纯属搞笑.(额,好吧,其实这个也不怎么正经) 就先说说day2吧: T1:这个东西应该叫做数论吧. 然而我一看到就照着样例在纸上推了大半天(然而还是没有看出来这东西是个杨辉三角) 然后就 ...

  7. Noip 2016 Day 1 & Day 2

    Day 1 >>> T1 >> 水题直接模拟AC: 考察三个知识点:1.你能不能编程 2.你会不会取模 3.你脑子抽不抽 然而第一次评测还是90,因为当模运算时 “ en ...

  8. 清北 Noip 2016 考前刷题冲刺济南班

    2016 10 29 周六 第一天 %%%,%ZHX大神 上午,60分, 下午,爆零orz 2016 10 30 周天 第二天 炒鸡倒霉的一天 %%%,%ZHX大神 据大神第一天的题最简单. 上午,和 ...

  9. 基础算法(搜索):NOIP 2015 斗地主

    Description 牛牛最近迷上了一种叫斗地主的扑克游戏.斗地主是一种使用黑桃.红心.梅花.方片的A到K加上大小王的共54张牌来进行的扑克牌游戏.在斗地主中,牌的大小关系根据牌的数码表示如下:3& ...

随机推荐

  1. gd-jpeg: JPEG library reports unrecoverable error 解决办法

    Warning: imagecreatefromjpeg() [function.imagecreatefromjpeg]: gd-jpeg: JPEG library reports unrecov ...

  2. MyBatis Mapper 接口如何通过JDK动态代理来包装SqlSession 源码分析

    我们以往使用ibatis或者mybatis 都是以这种方式调用XML当中定义的CRUD标签来执行SQL 比如这样 <?xml version="1.0" encoding=& ...

  3. [51NOD1095] Anigram单词(map)

    题目链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1095 字典的单词在map中排序和不排序各存1次,查的时候相减. ...

  4. [HDOJ3718]Similarity(KM算法,二分图最大匹配)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3718 题意:有一堆答题情况和正确答案,问每一个答题情况的正确率最大是多少. 给每一对答案和答题情况的字 ...

  5. UML分析与设计

    考点: 掌握面向对象的分析与设计 掌握UML描述方法 用例图.类图.序列图.状态转换图 类图:类的属性.方法的识别:类间的各种关系 类图:实体.联系 各种关系图例: 泛化:取公共属性 关联分为聚合.组 ...

  6. Logical Databases逻辑数据库

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  7. Practical Java

    声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...

  8. FJNU 1156 Fat Brother’s Gorehowl(胖哥的血吼)

    FJNU 1156 Fat Brother’s Gorehowl(胖哥的血吼) Time Limit: 1000MS   Memory Limit: 257792K [Description] [题目 ...

  9. HDU 1698 Just a Hook(线段树成段更新)

    Just a Hook Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Tota ...

  10. Image与byte[]之间的转换

    //将image转化为二进制 public static byte[] GetByteImage(Image img) { byte[] bt = null; if (!img.Equals(null ...