P5279 [ZJOI2019]麻将
题面
今天,可怜想要打麻将,但是她的朋友们都去下自走棋了,因此可怜只能自己一个人打。可怜找了一套特殊的麻将,它有 种不同的牌,大小分别为 到 ,每种牌都有 张。
定义面子为三张大小相同或者大小相邻的麻将牌,即大小形如 或者。定义对子为两张大小相同的麻将牌,即大小形如 。
定义一个麻将牌集合 是胡的当且仅当它的大小为 且满足下面两个条件中的至少一个:
- 可以被划分成五个集合 至 。其中 为对子, 至 为面子。
- 可以被划分成七个集合 至 ,它们都是对子,且对应的大小两两不同。
可怜先摸出了 张牌,并把剩下的 张牌随机打乱。打乱是等概率随机的,即所有种排列都等概率出现。
对于一个排列 ,可怜定义 为可怜事先摸出的 张牌加上 中的前 张牌构成的集合,定义 的权值为最小的 满足 存在一个子集是胡的。
现在可怜想要训练自己的牌效,因此她希望你能先计算出 的权值的期望是多少。
数据范围: 。
题解
感觉算是DP套DP中很高级的了(虽然总共也只见过两题...)
主要是理解DP套的那一个DP是在干什么。
其实 AC自动机结合DP 就是一个DP套DP。(如果知道这个东西那么你完全按理解它的方式理解是完全可以的)
因为内层的DP实际上就是一个自动机的形式。
比如这题中我们需要的就是判断给定一些牌,判断是否是胡牌(AC自动机内是需要判断当前字符串是否包含了给定的一些字符串)。
以下是怎么判断一副牌是胡的。
如果只要判断是否胡牌,那么有用的信息只有每个牌有几张。
所以相当于我们现在只需要输入一个长度为 的数组 ,其中 表示大小为 的有 张。
现在考虑我们需要维护什么。
-
第一种胡法,我们可以设 表示处理完了第 种编号的牌,我们预留了 对形如 的牌,和 个编号为 的牌, 表示之前的决策过程当中有没有预留过对子,dp当中存储的值是这种情况下的最大面子数。
并且三个相同的顺子可以被算成三个连续的刻子,所以 和 这两维都不会超过 。
转移是比较简单的。
-
第二种胡法,我们只需要额外记录一个变量 表示出现的对子的数量就行。
但是到现在为止我们也只是知道了怎么判断和牌,和题目要求的东西还相差甚远。
别急,还记得 AC自动机+DP 如何处理的吗,我们是判断 是否合法(不包含给定字符串),其中 为当前状态, 为枚举的下一位是什么字符。
所以,这题我们也可以把所有可能的 胡牌状态 记录下来,构造 ,然后转移。
具体来说,就是上面提到的 ,我们把它记成一个结构体,用map去重、标号。
下面代码省去了重载和初始化。
struct qqq{
int f[3][3];
int* operator [] (const int &x){
return f[x];
}
void change(qqq &x,const int &y){//下一个大小的牌有 y 张。
for(int i=0;i<3;++i)
for(int j=0;j<3;++j)
if(f[i][j]!=-1)
for(int k=0,la=y-i-j;la>=0&&k<=2;--la,++k)
x[j][k]=max(x[j][k],min(i+f[i][j]+la/3,4));
}
bool pd(){
for(int i=0;i<3;++i)
for(int j=0;j<3;++j)
if(f[i][j]>=4)return 1;
return 0;
}
};
struct qq{
qqq f,g;//f:没有预留对子,g:有预留对子
int cnt;//对子的数目
bool pd(){//判断是否胡牌
if(cnt>=7)return 1;
return g.pd();
}
}a[maxn];
qq ne2(qq x,const int &y){//下一个大小的牌有 y 张。
qq ans;ans.cl();
ans.cnt=x.cnt+(y>=2);
if(y>=2)x.f.change(ans.g,y-2);//可以预留对子
x.f.change(ans.f,y),x.g.change(ans.g,y);
return ans;
}
map<qq,int> id;
int ne[maxn][5];
queue<int> p;
void sous(){
a[0].cl(),id[a[0]]=0;
p.push(0);
while(p.size()){
int x=p.front();p.pop();
for(int i=0;i<=4;++i){
qq y=ne2(a[x],i);
if(!id.count(y)){
if(y.pd())ne[x][i]=id[y]=-1;
else{
ne[x][i]=id[y]=++top,a[top]=y;
p.push(top);
}
}
else ne[x][i]=id[y];
}
}
}
现在我们就可以仿照 AC自动机+DP 去设状态了,
一个很套路的转化: 答案 ,其中 表示摸了的牌数(不包括本来就有的 张)大于等于 的方案数。
所以就可以设 表示当前考虑到了大小为 的牌,摸了 张,当前胡牌状态是 ,转移是简单的。
启发
- DP套DP的一个记录?
本文作者:qwq123
本文链接:https://www.cnblogs.com/qwq-123/p/16062173.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步