[hdu1133]Buy the Ticket(dp)

本文探讨了一道关于购票排队的算法题目,重点在于如何通过动态规划求解最优排队方案数,并利用高精度计算处理极大的数值结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

传送门
题意:每张门票卖50元。现在有n个拿50元的人和m个拿100元的人(认为每个人是不同的)排队买票,售票处起初并没有零钱可以找。问有多少种排队方案使所有的人都可以顺利买到票。

题解

我记得这道题在很久很久以前是Loli某一次奇怪的模拟赛。。。然而当时并不会。。。现在把它重新翻出来a掉感觉很开心。
其实就是要保证无论何时第一种人的前缀和一定比第二种人多。我们把这两种人用0/1来表示。
设f(i,j,k)表示前i个人,其中有j个人是0,k个人是1的方案数,规定j+k=i。
那么f(i,j,k)=f(i-1,j-1,k)+f(i-1,j,k-1)。因为j+k=i,这里实际上是 O(n2) 的。
然后最终的答案为f(n+m,n,m)*n!*m!,也就是因为人是不同的,把所有的0和1全排一下。
答案非常大,要用到高精度并且压位优化,否则非常慢。担心空间爆炸的话f是可以滚动的。
最好是刚开始预处理好1~100的所有f值然后每次询问直接输出。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;

int n,m,Case;
struct hp{int a[405];}f[2][105][105],g[105][105],mul[105];

hp jia(hp a,hp b)
{
    hp ans;memset(ans.a,0,sizeof(ans.a));
    ans.a[0]=max(a.a[0],b.a[0]);
    for (int i=1;i<=ans.a[0];++i) ans.a[i]=a.a[i]+b.a[i];
    for (int i=1;i<=ans.a[0];++i)
    {
        ans.a[i+1]+=ans.a[i]/10000;
        ans.a[i]%=10000;
    }
    if (ans.a[ans.a[0]+1]) ans.a[0]++;
    return ans;
}
hp cheng(hp a,hp b)
{
    hp ans;memset(ans.a,0,sizeof(ans.a));
    for (int i=1;i<=a.a[0];++i)
        for (int j=1;j<=b.a[0];++j)
            ans.a[i+j-1]+=a.a[i]*b.a[j];
    ans.a[0]=a.a[0]+b.a[0]-1;
    for (int i=1;i<=ans.a[0];++i)
    {
        ans.a[i+1]+=ans.a[i]/10000;
        ans.a[i]%=10000;
    }
    while (ans.a[ans.a[0]+1])
    {
        ans.a[0]++;
        ans.a[ans.a[0]+1]+=ans.a[ans.a[0]]/10000;
        ans.a[ans.a[0]]%=10000;
    }
    return ans;
}

int main()
{
    mul[0].a[0]=mul[0].a[1]=1;
    for (int i=1;i<=100;++i)
    {
        hp now;memset(now.a,0,sizeof(now.a));
        now.a[0]=1,now.a[1]=i;
        mul[i]=cheng(mul[i-1],now);
    }
    f[1][1][0].a[0]=f[1][1][0].a[1]=1;
    for (int i=2;i<=200;++i)
    {
        for (int j=0;j<=min(i,100);++j)
        {
            int k=i-j;if (k<0||k>j) continue;
            f[i&1][j][k]=jia(f[(i-1)&1][j][k-1],f[(i-1)&1][j-1][k]);
            g[j][k]=cheng(f[i&1][j][k],mul[j]);
            g[j][k]=cheng(g[j][k],mul[k]);
        }
    }

    while (~scanf("%d%d",&n,&m))
    {
        if (!n&&!m) break;
        hp ans=g[n][m];
        printf("Test #%d:\n",++Case);
        if (!ans.a[0])
        {
            puts("0");
            continue;
        }
        for (int i=ans.a[0];i>=1;--i)
        {
            if (i==ans.a[0]) printf("%d",ans.a[i]);
            else
            {
                if (ans.a[i]<10) printf("000%d",ans.a[i]);
                else if (ans.a[i]<100) printf("00%d",ans.a[i]);
                else if (ans.a[i]<1000) printf("0%d",ans.a[i]);
                else printf("%d",ans.a[i]);
            }
        }
        putchar('\n');
    }
}

总结

①memset能少用就少用。那些能覆盖的值不要轻易memset,否则非常慢。
②压位优化的0的个数不要搞错了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值