hihoCoder 1159 扑克牌 编程之美2015初赛第二场

这一题DP的状态真难想>.<看了大神的博客想了好久才明白。

我们考虑这个process,从input一堆牌中逐个选择一张从左到右放在空位上。定义状态(a,b,c,d,z),a表示相同面值总共有1张的牌有几种,b、c、d分别表示相同面值的牌总共有2、3、4张的种类数,a、b、c、d即为四个等价类。每一种牌最多四个花色,且没有重复的牌,所以最多4个等价类。

z表示前一个位置选择的牌在当前状态下属于相同面值总共有z张的等价类。初始状态中z=0。

样例中的AC AD AS JC JD KD对应的状态为(1,1,1,0,0),如果选择了AC,下一个状态为(1,2,0,0,2)

考虑以下四个disjoint case,

case 1:当前状态选择一个相同面值只有1张的牌,那么下一个状态等价类a的元素个数少了一个,且当前选择的牌在下一个状态中属于等价类0。因为相邻牌面值不同,如果前一个位置选择的牌当前属于等价类a(上一次选择的时候有a+1张),那么现在只有a-1种选择,否则有a种选择。对应的种类数为dp[a-1,b,c,d,0]*(z==1?a-1:a)*1。

case 2:当前状态选择一个相同面值只有2张的牌,那么下一个状态等价类b的元素个数少了一个,等价类a的元素多了一个。且当前选择的牌在下一个状态中属于等价类1。因为有两种花色,最后还要乘上对应的排列数2。对应的种类数为dp[a+1,b-1,c,d,1]*(z==2?b-1:b)*2。

case 3:当前状态选择一个等价类c中的牌,对应的种类数为dp[a,b+1,c-1,d,2]*(z==3?c-1:c)*3。

case 3:当前状态选择一个等价类d中的牌,对应的种类数为dp[a,b,c+1,d-1,3]*(z==4?d-1:d)*4。

边界条件为没有牌可选时,只有当前的一种选择。

纠结了好久题目中要求对2^64取模肿么处理,肯定超过long long了,而且unsigned long long最大值是2^64-1,还以为要用大数。后来发现unsigned溢出以后不会变成负数,正好和mod 2^64的结果相同。学渣微机没学好的后果>.<

在每个test case都初始化一遍dp数组会TLE。实际上没必要每次都重新计算,因为某一状态不会受input data的变化而改变。

#include<iostream>
#include<stdio.h>
#include<cstdio>
#include<stdlib.h>
#include<vector>
#include<string>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<queue>
#include<ctype.h>
#include<map>
#include<time.h>
#include<bitset>
#include<set>
#include<list>
using namespace std;
//hiho 1159

const int maxn=14;
int T;
int N;
//2^64=18446744073709551616;
int card[20];//num of certain cards
unsigned long long dp[maxn][maxn][maxn][maxn][5];
bool used[maxn][maxn][maxn][maxn][5];
unsigned long long ans;
int amount[5];
unsigned long long dfs(int a,int b,int c,int d,int z)
{
    if(used[a][b][c][d][z]==true)
    {
        return dp[a][b][c][d][z];
    }
    if(a==0&&b==0&&c==0&&d==0)
    {
        used[a][b][c][d][z]=true;
        return dp[a][b][c][d][z]=1;
    }
    unsigned long long ret=0;
    if(a>0)
    {
        ret+=dfs(a-1,b,c,d,0)*(z==1?a-1:a);
    }
    if(b>0)
    {
        ret+=dfs(a+1,b-1,c,d,1)*(z==2?b-1:b)*2;
    }
    if(c>0)
    {
        ret+=dfs(a,b+1,c-1,d,2)*(z==3?c-1:c)*3;
    }
    if(d>0)
    {
        ret+=dfs(a,b,c+1,d-1,3)*(z==4?d-1:d)*4;
    }
    used[a][b][c][d][z]=true;
    return dp[a][b][c][d][z]=ret;
}
int main()
{
    freopen("input.txt","r",stdin);
    scanf("%d",&T);
    memset(dp,0,sizeof(dp));//每一个test case都计算会TLE
    memset(used,false,sizeof(used));
    for(int ca=1;ca<=T;ca++)
    {
        scanf("%d",&N);
        memset(card,0,sizeof(card));
        //memset(dp,0,sizeof(dp));
        memset(amount,0,sizeof(amount));
        //memset(used,false,sizeof(used));
        ans=0;
        char ch[5];
        for(int i=0;i<N;i++)
        {
            scanf("%s",ch);
            if(ch[0]>='2'&&ch[0]<='9')
            {
                card[ch[0]-'0']++;
            }
            //T J Q K A
            else if(ch[0]=='T')
            {
                card[10]++;
            }
            else if(ch[0]=='J')
            {
                card[11]++;
            }
            else if(ch[0]=='Q')
            {
                card[12]++;
            }
            else if(ch[0]=='K')
            {
                card[13]++;
            }
            else if(ch[0]=='A')
            {
                card[14]++;
            }
        }
        for(int i=2;i<=14;i++)
        {
            if(card[i]!=0)
            {
                amount[card[i]]++;
            }
        }
       // cout<<amount[1]<<" "<<amount[2]<<" "<<amount[3]<<" "<<amount[4]<<endl;
        ans=dfs(amount[1],amount[2],amount[3],amount[4],0);
        printf("Case #%d: %llu\n",ca,ans);//I64d will lead to WA
    }
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值