CH5E26 扑克牌 (计数类DP)

Posted 812-xiao-wen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CH5E26 扑克牌 (计数类DP)相关的知识,希望对你有一定的参考价值。

\(CH~5E26~*~\)扑克牌: (计数类DP)

技术图片



\(solution:\)

唉,计数类DP总是这么有套路,就是想不到。

这道题我们首先可以发现牌的花色没有价值,只需要知道每种牌有 \((0,4]\) 张,牌的面值也不用管,只需要知道总共有 \((0,13]\) 种牌。然后我们就可以设出状态: \(f[i][j][p][q]\) 表示还剩 \(1\) 张有 \(i\) 种牌的,还剩 \(2\) 张牌的有 \(j\) 种,还剩 \(3\) 张牌的有 \(p\) 种,还剩 \(4\) 张牌的有 \(q\) 种,

可能有人会问,怎么想到这种方法的。事实上,这是计数类DP的重点。做题目的时候,我们往往需要去题目里挖掘性质,将题目所给条件进行转化或建模 。然后在数据范围上大胆猜想,更具经验判断。计数类DP,博主目前只会两个套路:

  1. 一个是找基准点(一般用记忆化搜索代替DP)
  2. 然后就是这一种,直接构造法,我们尤其是构造特殊排列的时候。类比这一题,我们可以用轮廓线DP来模拟构造题目所需排列,轮廓线DP里保存的就是构造到某一阶段所剩余(或用了)的元素。就像25语言那道题目。

所以这一道题我们可以枚举放牌的过程,每一次将牌放到排列末尾。当然对于不同的构造方法,轮廓线DP的实现各有千秋。这一道题之所以难,还在于我们如何让转移达到题目要求。题目里唯一一个棘手的限制就是,相邻的牌面值不同。这个情况如果我们只设四维显然不能明确表示出构造到某一阶段的具体状态。所以我们再加一维用来表示当前排列最后一张牌是哪一张几张牌了。

想必大家读到这里应该已经知道如何转移了吧,我们的下一张牌(就是转移),会有四种情况:(要注意我们多加的那一维的影响)

  1. 放到排列末尾的那种牌已经只剩一张了,
  2. 还剩两张
  3. 还剩三张
  4. 还剩四张

然后,计数类DP,往往可以用记忆化搜索来带替DP,比较方便。



\(code:\)

#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>

#define ull unsigned long long
#define ll long long
#define db double
#define rg register int

using namespace std;

int n;
int ch[75],g[5];
ull f[15][15][15][15][5];

inline int qr()
    register char ch; register bool sign=0; rg res=0;
    while(!isdigit(ch=getchar()))if(ch=='-')sign=1;
    while(isdigit(ch))res=res*10+(ch^48),ch=getchar();
    if(sign)return -res; else return res;


inline ull ask(int a,int b,int c,int d,int r)
    ull res=f[a][b][c][d][r]; if(res)return res;
    if(a)res=res+ask(a-1,b,c,d,1)*(a-(r==2));
    if(b)res=res+ask(a+1,b-1,c,d,2)*2*(b-(r==3));
    if(c)res=res+ask(a,b+1,c-1,d,3)*3*(c-(r==4));
    if(d)res=res+ask(a,b,c+1,d-1,4)*4*d;
    return f[a][b][c][d][r]=res;


int main()
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    for(rg i=0;i<=4;++i)f[0][0][0][0][i]=1;
    rg t,ff=0; t=qr(); 
    while(t--)
        n=qr(); string s;
        for(rg i=1;i<=n;++i) cin>>s,++ch[s[0]-49];
        for(rg i=1;i<=4;++i) g[i]=0;
        for(rg i=0;i<=50;++i) ++g[ch[i]],ch[i]=0;
        printf("Case #%d: ",++ff);
        cout<<ask(g[1],g[2],g[3],g[4],0)<<endl;;
    
    return 0;

以上是关于CH5E26 扑克牌 (计数类DP)的主要内容,如果未能解决你的问题,请参考以下文章

线性dp决策优化CH5E02

线性dp决策优化CH5E02

CH5E07 划分大理石(背包dp+二进制拆分)

洛谷 P1358 扑克牌

洛谷——P1358 扑克牌

剑指 Offer 61. 扑克牌中的顺子