qwertyxk | 1221 | Accepted | 888K | 0MS | C++ | 798B | 2013-01-14 12:01:00 |
先来看分析
这个题目和昨天写的3181题目是相同的,也是一个整数划分问题,于是我们可以把那个dp的方法运用过来
这个题目需要做一点点变形,分奇数和偶数来讨论
POJ 1221 的大意是:回文序列是指从左向右读和从右向左读都是一样的序列。而单峰回文序列则是该序列从左和从右向中间单调非递减的序列。例如,题号 1 2 2 1 就是一个单峰回文序列,如果把每个数字看成序列的元素。现在,给定整数 n ,求 n 的单峰回文序列划分的个数。
首先,我们注意到,回文序列有两中:序列的长度为奇数和偶数的回文序列。显然,奇数 n 只可能有长度为奇数的单峰回文划分,而偶数则可以有长度为奇数的回文划分,也可以有长度为偶数的回文划分。我们先看长度为奇数的回文划分,因为这是所有整数都可以有的。
对于长度为奇数的回文划分,如果两个序列的中间的元素值不同,则两个划分肯定不同(这个不难证明)。那么,当我们固定中间元素的值为 v 时,也就是固定了中间元素左边(或右边)的序列之和 ( s = (n-v) /2,这里,取 v 使得 s 为整数 ) 时,有多少种不同的单峰回文划分呢?为了使得中间元素为 v 的回文划分是单峰的,则 s 的划分中所有元素都不能比 v 大。反过来,s 的每一个最大元素不超 v 的划分,都可以和 v 一起构成 n 的一个单峰回文划分!因此,问题就变成了求 f(s,v) !对所有可能的 (s,v) 求 f(s,v) 之和,则为 n 的长度为奇数的单峰回文划分的个数。
这样,当 n 为奇数时,问题已经得到解决。当 n 为偶数时,可以有长度为偶数的单峰回文划分。这些划分的左边之和等于右边等于 n/2。整数 s = n / 2 的每个不同的划分都可以用来构成 n 的一个不同单峰回文划分,因此,长度为偶数的单峰回文划分的个数为 f(s,s)。至此,POJ 1221 得到完满解决,而核心问题则是解决整数的划分,即求 f(n,m)。
这个f(n,m)已经得到完整的解决,参考 http://blog.csdn.net/qwerty_xk/article/details/8499011
源代码如下:
#include<stdio.h>
#include<string.h>
__int64 dp[300][300];
int min(int a,int b)
{
return a<b?a:b;
}
int main()
{
int n,i,j;
memset(dp,0,sizeof(dp));
for(i=0;i<300;i++) //先对以后要用到的dp都求出来,在数组中保存好
dp[0][i]=1;
for(i=1;i<300;i++)
{
for(j=1;j<300;j++)
{
if(j-i<0)
{
dp[j][i]=dp[j][i-1];
continue;
}
dp[j][i]=dp[j][i-1]+dp[j-i][i];
}
}
while(scanf("%d",&n))
{
if(n==0)
break;
__int64 sum=0;
if(n%2!=0) //奇数时候的情形
{
for(int v=1;v<n;v++)
{
if((n-v)%2==0) //要能做到左右对称,故必须能整除
{
int s=(n-v)/2;
sum+=dp[s][v];
}
}
}
else //偶数时候的情形
{
int s;
for(int v=1;v<n;v++) //分奇偶数列来进行讨论
{
if((n-v)%2==0)
{
s=(n-v)/2;
sum+=dp[s][v];
}
}
s=n/2; //偶数序列
sum+=dp[s][s];
}
sum+=1; //最后别忘了把自己本身这个数加上
printf("%d %I64d\n", n,sum);
}
return 0;
}