Description
假设有N个数写成一行,这行上面一行有N -1个数,第i数是第一行第i个数和第i+1个数的和。依次类推,最上面一行为第N行,有一个数。例如,4个数2,1,2,4形成如下结构:
15
6 9
3 3 6
2 1 2 4
我们称这种结构为NumberPyramid。两个NumberPyramid相同当且仅当对应位置上的数相同。
给出两个整数baseLength和top。计算出有多少个不同的仅包含正整数的NumberPyramid,使得NumberPyramid的最高的数为top,第一行有baseLength个数。由于答案可能过大,只需要输出模1,000,000,009的余数即可。
Input
第一行两个整数baseLength,top。
Output
一个整数,题目所求答案。
Sample Input
【样例输入1】 3 5
【样例输入2】 5 16
Sample Output
【样例输出1】 2
【样例输出2】 1
Hint
【数据范围】
对于30%的数据,top<=20,baseLength<=5。
对于100%的数据,2<=baseLength<=1,000,000,1<=top<=1,000,000。
这道题特别厉害..
容易发现,这就是一个加权杨辉三角,底层每一个数对顶层答案的贡献为c[n][i]*a[i],a[i]表示底层数字。
思路1:从top开始分割,用搜索来得到解的个数,加上适当的剪枝,可能拿得到30分。
思路2:考虑每个底层数字的贡献,我们发现这是一个n元的不定方程,每一项的系数为c[n][i],方程右边=top。
我们主要着手优化思路2。
这个数据够吓人,baselen长达100W,但是仔细分析下非常容易发现,一旦baselen大于一定的数值,top会大到惊人,我们举最小的例子:底层全都为1,那么top为2^(n-1),这样一来,一旦n>21,top的值就会超过100W,而题目数据给出的top范围恰恰是100W。
那么我们就可以把问题砍掉一大半:当n>21的时候,直接输出0,因为不存在这么大的top。
我们已经把原问题转化为了一个不超过21元的不定方程,当然这可不是一个数学问题(其实在系数已经确定的情况下可以直接暴力搜索了,但是top仍然高达100W,虽然说系数也不小,但枚举的量仍然庞大)。
进一步进行优化,其实没必要打到100W,前面都有提到,最小的top为(1<<(n-1)),所谓的更大无非是给杨辉三角加了一个系数,我们可以直接把top减去(1<<(n-1)),剩下的搜索也好,背包也罢,以杨辉三角为底数进行加倍枚举都能轻松通过所有数据。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#define mod 1000000009
using namespace std;
inline int read()
{
int bj=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')bj=-1;
ch=getchar();
}
int ret=0;
while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
return ret*bj;
}
int n,top,c[105][105]={0};
int f[1000005]={0};
void C()
{
for(int i=1;i<=100;i++)c[i][1]=c[i][i]=1;
for(int i=3;i<=20;i++)
{
for(int j=2;j<i;j++)
{
c[i][j]=c[i-1][j-1]+c[i-1][j];
}
}
}
int main()
{
cin>>n>>top;
if(n>21)
{
cout<<0;
return 0;
}
C();
f[0]=1;
top-=(1<<(n-1));
for(int i=1;i<=n;i++)
{
for(int j=c[n][i];j<=top;j++)
f[j]=(f[j]+f[j-c[n][i]])%mod;
}
cout<<f[top];
return 0;
}
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#define mod 1000000009
using namespace std;
inline int read()
{
int bj=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')bj=-1;
ch=getchar();
}
int ret=0;
while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
return ret*bj;
}
int n,top,c[105][105]={0};
int f[1000005]={0};
void C()
{
for(int i=1;i<=100;i++)c[i][1]=c[i][i]=1;
for(int i=3;i<=20;i++)
{
for(int j=2;j<i;j++)
{
c[i][j]=c[i-1][j-1]+c[i-1][j];
}
}
}
int main()
{
cin>>n>>top;
if(n>21)
{
cout<<0;
return 0;
}
C();
f[0]=1;
top-=(1<<(n-1));
for(int i=1;i<=n;i++)
{
for(int j=c[n][i];j<=top;j++)
f[j]=(f[j]+f[j-c[n][i]])%mod;
}
cout<<f[top];
return 0;
}
这里用的背包思想,注意一下j的枚举顺序,要正着枚举。
搜索应该没问题吧?估计不会有人去写的..
这个题的优化特别厉害,也不是说想不到,认真思考下肯定能够有所发现,倒是最后的背包思想值得学习。(搜索赛高!)