poj 7222 怀表问题(递推+空间压缩)

程序设计实习2015上机练习43题第42题
总时间限制: 1000ms 内存限制: 32768kB

描述
怀表是由一个表链和一个表盘连接而成的。同时,表链又是由多个长度为1的表链组件连接而成的。小明现有一个表盘和多个长度为1的表链组件。他希望组合成一个完整的怀表当做生日礼物送给爸爸。

表盘和表链组件的两端都有表扣可以和其它的表链组件或者表盘相连。 一共有两种表扣设计,分别是“L” 和“V”。表链组件和表盘的左右两端可以使用相同的或者不同的表扣设计,因此我们有以下四种不同的表链组件和表盘设计方式,“LL”, “LV”, “VL”, “VV”,如“LV”表示左端的表扣设计是”L”, 右端的表扣设计是“V”。另,表盘和表链是不允许上下左右翻转的。如果把两个表链组件或者一个表链组件和表盘相连接,那么相连部分的表扣设计必须一致。此外,还必须确保表盘可以连接到表链的任意一端。

例子1:表盘是“LV”,共有5个表链组件分别是,“LL”, “LL”, “LV”, “VL”, “VL” 。现在希望组合成长度为4的怀表(表链长度是4)。 有两种正确的表链组合方式:“VLLLLVVL” 和“VLLVVLLL”。而且,对于每一种表链的组合方式,表盘都可以连接到表链的左右两端 (表盘放到表链左端使用V相连,表盘放到表链右端使用L相连)。

例子2: 表盘是“LL”,共有4个表链组件分别是,“LL”, “LV”, “VL”, “VV”。现在希望组合成长度为3的怀表(表链长度是3)。有三种正确的表链组合方式:“LLLVVL”, “LVVLLL”和“LVVVVL”。而且,对于每一种表链的组合方式,表盘都可以接到表链的任一端.

注意怀表的组合方式可能不唯一。每一种表链的组合方式都可以当做是一个由”L”和“V”

组成的字符串。如果两种表链对应的字符串是不匹配的, 那么表链也是不相同的。

输入
标准的输入包含若干组测试数据。每组数据第一行是整数N (0 < N ≤ 40)和K (0 < K ≤ N) 。
N 代表表链组件的个数,K代表要组合成的怀表长度(表链组件的个数)。 接着的N+ 1 行描述表盘(第二行)和表链组件的表扣设计。有四种可能输入: “LV”, “LL”, “VV” 和“VL”。

输出
对于每组测试数据,输出一行。 输出“YES”, 如果可能按照要求组合成怀表,并输出可能的组合方式的数目。如果不能,则输出”NO”.

样例输入

4 4
LV
LL
LV
VL
VL
4 4
VL
LL
LV
VL
VL
样例输出

YES
2
NO
提示
提示: 本题需要使用 long long 数据类型。

最近在重新练习程序设计实习43题,这里剩下一些有难度的题目很好。主要是怀表问题,选择客栈,潘多拉星球的悬浮公寓。前两个有些递推/数学算法在里头,最后一个题目很精彩,虽然算法很熟悉,但是细节不好处理。
哎,首先我审题不仔细的老毛病没有改尤其是当题目似乎比较熟悉的时候就没有动脑子了…各种理解错误,幸好最后又读了一遍题目总算改出了这些低级错误,但是考试的时候恐怕题目一难,心里一慌,时间不够就没有那么幸运了吧。所以以后一定要审题仔细。
这个题目,首先想到递推,用dp[i][n0][n1][n2][n3]表示两头为i时候四种表为n1,n2,n3,n4个时候种数(注意要开long long int,看提示)。但是要记录的数据比较多,简单计算一下空间Memory=sizeof(long long int)∗4∗414=64∗4∗414≈28∗404≈216∗10000=80000kB所以说空间会超,压缩比至少在4以上比较安全,那么应该怎么压缩空间呢?有两种思路压缩空间,两种压缩空间思路分别体现为version 1与version 2(同时version 1,version 2)实现也不一样。

version 1,用dp实现递推,然后注意到虽然每一个都可能有40个,但是总和不超过40,所以最大值之外只能有20个以内,那么不妨设最大值开头为L,不然所有表反一下号也不影响。那么开数组记载数目分量大概开成[40][40][20][20]就可以了,压缩比约等于4。这种做法压缩比不会太大只能勉强卡进,但是时间上有优势,可以一次预处理一直用过去,而且写起来容易一些。

version 2,用记忆化搜索实现递推,然后不直接管理一个高维数组,相反地自己编号来实现,编号最优大概在n1∗n2∗n3∗n4<=(n1+n2+n3+n44)4=104,压缩比高达4^4近200!编号可以说编号到了最优。但是这样时间上就得每一组数据自己算一算了。就不能像version 1一样一次预处理了,但是空间上特别优秀。另外写version 2时记忆化搜索也运用了搜索之长处,加上统一处理没有分类讨论,代码比较简洁,而且由于算法设计得当,加上空间申请很少,所以时间上反而由于version 1。version 2是一个时间空间代码复杂度都很好的代码,构思及其精巧。

version 1与version 2优劣势可以从运行数据看出,这是两次我自己独立写出来的,都很精彩,希望以后我能多做这些好题,写出精彩的算法。

version 1

Accepted 14360kB 50ms 2081 B G++

#define LL 0
#define LV 1
#define VL 2
#define VV 3
#define MAX_N 22

#include<stdio.h>
#include<memory.h>

int n,k;
int num[4],target;//L:0,V:1
long long int dp[4][MAX_N*2][MAX_N*2][MAX_N][MAX_N],ans;

void initialize()
{
    dp[0][2][1][1][1]=1;
    dp[1][1][2][1][1]=1;
    dp[2][1][1][2][1]=1;
    dp[3][1][1][1][2]=1;
    for (int ll=1;ll<MAX_N*2;ll++)
      for (int lv=1;lv<MAX_N;lv++)
        for (int vl=1;vl<MAX_N;vl++)
          for (int vv=1;vv<MAX_N;vv++)
            if (ll+lv+vl+vv>5)  
            {
                dp[LL][ll][lv][vl][vv]=dp[LV][ll][lv][vl-1][vv]+dp[LL][ll-1][lv][vl][vv];
                dp[LV][ll][lv][vl][vv]=dp[LV][ll][lv][vl][vv-1]+dp[LL][ll][lv-1][vl][vv];
                dp[VL][ll][lv][vl][vv]=dp[VV][ll][lv][vl-1][vv]+dp[VL][ll-1][lv][vl][vv];
                dp[VV][ll][lv][vl][vv]=dp[VV][ll][lv][vl][vv-1]+dp[VL][ll][lv-1][vl][vv];
            }
    return;
}

inline int code(char ch1,char ch2)
{
    return ((ch1=='V')<<1)+(ch2=='V');
}

int main()
{
    //freopen("input.txt","r",stdin); 
    char ch1,ch2;
    int j,nt[4];
    initialize();
    /*
    freopen("output.txt","w",stdout); 
    for (int ll=1;ll<=3;ll++)
     for (int lv=1;lv<=3;lv++)
      for (int vl=1;vl<=3;vl++)
       for (int vv=1;vv<=3;vv++)
        printf("ll=%d,lv=%d,vl=%d,vv=%d:LL=%d,LV=%d,VL=%d,VV=%d\n",
        ll-1,lv-1,vl-1,vv-1,dp[LL][ll][lv][vl][vv],dp[LV][ll][lv][vl][vv],
        dp[VL][ll][lv][vl][vv],dp[VV][ll][lv][vl][vv]);
    return 0;
    */
    while (scanf("%d %d\n",&n,&k)==2)
    {
        ans=0;
        memset(num,0,sizeof(num));
        scanf("%c%c\n",&ch1,&ch2);
        target=code(ch2,ch1);
        for (int i=1;i<=n;i++)
        {
            scanf("%c%c\n",&ch1,&ch2);
            num[code(ch1,ch2)]++;
        }
        j=0;
        for (int i=LL;i<=VV;i++)
            if (num[i]>num[j])
                j=i;
        j=(j&2)+((j&2)>>1);
        for (int i=0;i<4;i++)
            nt[i]=num[i];
        for (int i=0;i<4;i++)
            num[i]=nt[i^j];
        target^=j;
        //printf("%d %d %d %d:%d",num[0],num[1],num[2],num[3],target); 
        for (int ll=0;ll<=num[LL];ll++)
            for (int lv=0;lv<=num[LV] && ll+lv<=k;lv++)
                for (int vl=0;vl<=num[VL] && ll+lv+vl<=k;vl++)
                    if (k-ll-lv-vl<=num[VV])
                        ans+=dp[target][ll+1][lv+1][vl+1][k-ll-lv-vl+1];
        if (ans)
            printf("YES\n%lld\n",ans);
        else
            printf("NO\n");
    }
    return 0;
}


version 2

Accepted    784kB   0ms 1168 B

#include<stdio.h>

long long F[20000][4];//必须优化储存方式以防MLE 
int t[4];//VV:0,VL:1,LV:2,LL:3

inline int g(int x,int y,int z,int w)
{
    return (((x*(t[1]+1)+y)*(t[2]+1)+z)*(t[3]+1)+w);
}

long long f(int* num,int k,int goal)
{
    if (num[0]<0 || num[1]<0 || num[2]<0 || num[3]<0)
        return 0;
    if (F[g(num[0],num[1],num[2],num[3])][goal]!=-1)
        return F[g(num[0],num[1],num[2],num[3])][goal];
    if (k==1)
        return (num[goal]>0);
    int temp[4]={num[0],num[1],num[2],num[3]};
    int l=goal&2,r=goal&1;
    temp[l]--;
    F[g(num[0],num[1],num[2],num[3])][goal]=f(temp,k-1,r);
    temp[l]++;
    temp[l+1]--;
    F[g(num[0],num[1],num[2],num[3])][goal]+=f(temp,k-1,2+r);
    return F[g(num[0],num[1],num[2],num[3])][goal];
}

int main()
{
    int n,k,goal;
    char c1,c2;
    while (scanf("%d %d\n",&n,&k)!=EOF)
    {
        scanf("%c%c\n",&c1,&c2);
        for (int i=0;i<20000;i++)   
            F[i][0]=F[i][1]=F[i][2]=F[i][3]=-1;
        t[0]=t[1]=t[2]=t[3]=0;
        goal=((c2=='L')<<1)+(c1=='L');//要求适配 
        for (int i=1;i<=n;i++)
        {
            scanf("%c%c\n",&c1,&c2);
            t[((c1=='L')<<1)+(c2=='L')]++;
        }
        long long ans=f(t,k,goal);
        if (ans)
            printf("YES\n%lld\n",ans);
        else
            printf("NO\n");
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值