【NOIP 2016】斗地主
题意
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】斗地主的更多相关文章
- NOIp 2016 总结
NOIp 2016 总结 -----YJSheep Day 0 对于考前的前一天,晚自习在复习图论的最短路和生成树,加深了图的理解.睡得比较早,养足精力明日再战. Day 1 拿到题目,先过一边,题目 ...
- [NOIP]2016天天爱跑步
[NOIP]2016天天爱跑步 标签: LCA 树上差分 NOIP Description 小C同学认为跑步非常有趣,于是决定制作一款叫做<天天爱跑步>的游戏.<天天爱跑步>是 ...
- Luogu 2668 NOIP 2015 斗地主(搜索,动态规划)
Luogu 2668 NOIP 2015 斗地主(搜索,动态规划) Description 牛牛最近迷上了一种叫斗地主的扑克游戏.斗地主是一种使用黑桃.红心.梅花.方片的A到K加上大小王的共54张牌来 ...
- NOIP 2016 迟来的满贯
17-03-22,雨 17-03-22,一个特别重要的日子 在这一天,本蒻攻克了NOIP 2016最难的一题,D1T2——天天爱跑步 实现了NOIP 2016的AK! YAYAYAYAYAYAY 自然 ...
- NOIP 2016 D2T2 蚯蚓](思维)
NOIP 2016 D2T2 蚯蚓 题目大意 本题中,我们将用符号 \(\lfloor c \rfloor⌊c⌋\) 表示对 \(c\) 向下取整,例如:\(\lfloor 3.0 \rfloor = ...
- noip 2016 提高组题解
前几天写的那个纯属搞笑.(额,好吧,其实这个也不怎么正经) 就先说说day2吧: T1:这个东西应该叫做数论吧. 然而我一看到就照着样例在纸上推了大半天(然而还是没有看出来这东西是个杨辉三角) 然后就 ...
- Noip 2016 Day 1 & Day 2
Day 1 >>> T1 >> 水题直接模拟AC: 考察三个知识点:1.你能不能编程 2.你会不会取模 3.你脑子抽不抽 然而第一次评测还是90,因为当模运算时 “ en ...
- 清北 Noip 2016 考前刷题冲刺济南班
2016 10 29 周六 第一天 %%%,%ZHX大神 上午,60分, 下午,爆零orz 2016 10 30 周天 第二天 炒鸡倒霉的一天 %%%,%ZHX大神 据大神第一天的题最简单. 上午,和 ...
- 基础算法(搜索):NOIP 2015 斗地主
Description 牛牛最近迷上了一种叫斗地主的扑克游戏.斗地主是一种使用黑桃.红心.梅花.方片的A到K加上大小王的共54张牌来进行的扑克牌游戏.在斗地主中,牌的大小关系根据牌的数码表示如下:3& ...
随机推荐
- gd-jpeg: JPEG library reports unrecoverable error 解决办法
Warning: imagecreatefromjpeg() [function.imagecreatefromjpeg]: gd-jpeg: JPEG library reports unrecov ...
- MyBatis Mapper 接口如何通过JDK动态代理来包装SqlSession 源码分析
我们以往使用ibatis或者mybatis 都是以这种方式调用XML当中定义的CRUD标签来执行SQL 比如这样 <?xml version="1.0" encoding=& ...
- [51NOD1095] Anigram单词(map)
题目链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1095 字典的单词在map中排序和不排序各存1次,查的时候相减. ...
- [HDOJ3718]Similarity(KM算法,二分图最大匹配)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3718 题意:有一堆答题情况和正确答案,问每一个答题情况的正确率最大是多少. 给每一对答案和答题情况的字 ...
- UML分析与设计
考点: 掌握面向对象的分析与设计 掌握UML描述方法 用例图.类图.序列图.状态转换图 类图:类的属性.方法的识别:类间的各种关系 类图:实体.联系 各种关系图例: 泛化:取公共属性 关联分为聚合.组 ...
- Logical Databases逻辑数据库
声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...
- Practical Java
声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...
- FJNU 1156 Fat Brother’s Gorehowl(胖哥的血吼)
FJNU 1156 Fat Brother’s Gorehowl(胖哥的血吼) Time Limit: 1000MS Memory Limit: 257792K [Description] [题目 ...
- HDU 1698 Just a Hook(线段树成段更新)
Just a Hook Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Tota ...
- Image与byte[]之间的转换
//将image转化为二进制 public static byte[] GetByteImage(Image img) { byte[] bt = null; if (!img.Equals(null ...