题目描述
传送门
题意:每张门票卖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的个数不要搞错了。