【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& ...
随机推荐
- Intent官方教程(6)常见Intent示例,启动日历,时钟,镜头等。
guide/components/intents-common.html 包含:Alarm Clock Calendar Camera Contacts/People App Email File S ...
- 使用mybatis完成通用dao和通用service
使用mybatis完成通用dao和通用service 概述: 使用通用dao和通用service可以减少代码的开发.可以将常用的增删改查放到通用dao中.对不同的or框架,基本上都有自己的实现如Spr ...
- HTML在IE中的条件注释
HTML在IE中的条件注释 HTML的条件注释在IE5中被首次引入,直到IE9.一直都是简单地判定用户浏览器(IE,非IE,IE版本)的一种手段,而在IE10的标准模式下,条件注释功能被停止支持(兼容 ...
- 【leetcode❤python】232. Implement Queue using Stacks
#-*- coding: UTF-8 -*-#双栈法class Queue(object): def __init__(self): """ ...
- Android 内部存储和外部存储
应用程序的一些配置文件需要存储在手机上.一般分为内部存储和SD卡存储. 一. 内部存储 ,以 FileOutputStream File file = new File(getFilesDir(),& ...
- 学习笔记TimePicker
new TimePickerDialog(this, new OnTimeSetListener() { @Override public void onTimeSet(TimePicker view ...
- Javascript this指针
Javascript是一门基于对象的动态语言,也就是说,所有东西都是对象,一个很典型的例子就是函数也被视为普通的对象. 前言 Javascript是一门基于对象的动态语言,也就是说,所有东西都是对 ...
- Verify an App Store Transaction Receipt 【苹果服务端 验证一个应用程序商店交易收据有效性】
转自:http://blog.csdn.net/saindy5828/article/details/6414014 1. 从Transaction 的TransactionReceipt属性中得到接 ...
- NoSQL聚合数据模型
NoSQL聚合数据模型 特点 聚合数据模型的特点就是把经常访问的数据放在一起(聚合在一块): 这样带来的好处很明显,对于某个查询请求,能够在与数据库一次交互中将所有数据都取出来: 当然,以这种方式存储 ...
- 转 图片资源加密,Lua文件加密
游戏开发中常遇到资源保护的问题. 目前游戏开发中常加密的文件类型有:图片,Lua文件,音频等文件,而其实加密也是一把双刃剑. 需要安全那就得耗费一定的资源去实现它.目前网上也有用TexturePack ...