NOIP 2015 Senior 3

来源:互联网 发布:unity3d插件map lab 编辑:程序博客网 时间:2024/05/29 13:41

题目1
题目2

先看这道题的题目概况:给的时间是2s,内存是1G(真大= =),所以往搜索方面去想,然后来分析一下搜索时怎么出牌。想想自己斗地主的时候,是不是能多出就多出啊?不对啊,有时候我们不一定会出一个炸弹,而是选择留下一张牌等着出顺子。如果不能出顺子呢?那我们就放心了,能一次多出几张就多出几张吧。因此发现:如果不出顺子,出牌次数是可以通过贪心获得最优解的。贪心策略就是能出炸弹就出炸弹,能出三张就出三张,能出一对就出一对,不要拆开了,同时带得越多越好。那么,搜索的对象就出来了。我们只需要搜该不该出顺子,该出怎样的顺子就好了。

可以这样设计dfs的框架:

void dfs(int discardTime){    最优性剪枝    计算不出顺子的次数,更新最优解    出顺子,深搜,回朔}

这种思路还是很简单,关键就看怎么写,看参考代码吧(其实我是参考了别人的,但是看懂了后真的好简单)。

参考代码

#include <cstdio>#include <cstdlib>#include <cmath>#include <cstring>#include <iostream>#include <algorithm>#include <vector>#include <string>#include <stack>#include <queue>#include <deque>#include <map>#include <set>using std::cin;using std::cout;using std::endl;inline int readIn(){    int a;    scanf("%d", &a);    return a;}int n;int ans;int card[15];int cnt[5];void dfs(int discard = 0){    if(discard > ans) return; //最优性剪枝    memset(cnt, 0, sizeof(cnt));    for(int i = 0; i <= 14; i++) cnt[card[i]]++; //使用这种方式计算不出顺子的情况好算得多    int noStraight = 0;    while(cnt[4]) //出炸弹    {        cnt[4]--;        noStraight++;        if(cnt[2] >= 2) cnt[2]-=2; //四带二对        else if(cnt[1] >= 2) cnt[1]-=2; //四带二张    }    while(cnt[3]) //出三张    {        cnt[3]--;        noStraight++;        if(cnt[2]) cnt[2]--; //三带二        else if(cnt[1]) cnt[1]--; //三带一    }    if(card[0] && card[1] && cnt[1] >= 2) noStraight--; //Joker是分开存的,但是可以单独打一对出去,在这里校正一下    noStraight += cnt[1] + cnt[2]; //加上没打的对子和单张牌    ans = std::min(noStraight + discard,ans); //更新最优解    for(int i = 3; i <= 14; i++) //从3到A,单顺子    {        int j = i;        for(; card[j] && j <= 14; j++)        {            card[j]--;            if(j - i + 1 >= 5) dfs(discard + 1); //如果多于5张向下搜索        }        while(j > i) card[--j]++; //回朔    }    for(int i = 3; i <= 14; i++) //双顺子    {        int j = i;        for(; card[j] >= 2 && j <= 14; j++)        {            card[j] -= 2;            if(j - i + 1 >= 3) dfs(discard + 1);        }        while(j > i) card[--j] += 2;    }    for(int i = 3; i <= 14; i++) //三顺子    {        int j = i;        for(; card[j] >= 3 && j <= 14; j++)        {            card[j] -= 3;            if(j - i + 1 >= 2) dfs(discard + 1);        }        while(j > i) card[--j] += 3;    }}int main(){    int a = readIn();    n = readIn();    while (a--)    {        ans = n;        memset(card, 0, sizeof(card));        for(int i = 1; i <= n; ++i)        {            int x=readIn();            int y=readIn();            if(!x) card[y - 1]++; //Joker分别放在0和1,因为在顺子中不能带Joker            else if(x == 1) card[14]++; //A放在14的位置,因为顺子的顺序是JQKA,刚好连上            else card[x]++; //其它的正常存        }        dfs();        printf("%d\n",ans);    }    return 0;}

所以这道题最关键的地方就是明确要枚举什么。不是什么都去枚举,而是只枚举顺子。一个良好的程序结构也很重要,往往能让人事半功倍。尽管写了这么久代码了,但事实上一些实用的结构还需要多积累才好。

原创粉丝点击