您的位置:首页 > 运维架构

一道topcoder题目的动态规划解

2009-06-25 17:53 375 查看
文章末尾为问题描述。

看到这道题的时候时间已经不多了,没多想,先对输入的segments排序,直接生成所有可能情况的组合,然后判断每个组合是否满足凸多边形的约束。结束以后分析一下,觉得太费时间了,找了个50个元素的segments和k为10的测试用例,运行时间居然要9分多钟,这显然挂了。

想想这个问题应该有更快的方法,突破点应该就是那些限制条件,就像整数背包问题可以用动态规划解一样。这个可以从公式C(n+1,m)=C(n,m)+C(n,m-1)说起(组合数公式,不知道怎么打公式上来),该公式可以理解为,从{1,2,...,n}选择m个的组合有C(n,m)个,这些组合为{v(1),v(2),...,v(C(n,m))},选择m-1个的组合有C(n,m-1)个,为{u(1),u(2),...,u(C(n,m-1))}。那么现在要求从{1,2,...,n,n+1}中选m个的所有组合,其实可以从前两个来构造,结果就是{v(1),v(2),...,v(C(n,m)), <u(1),n+1>,<u(2),n+1>,...,<u(C(n,m-1)),n+1>},这样就构造出从n+1个元素选m个的C(n+1,m)规模的所有组合来了。

再回到这道题,每个元素的范围是1到50000,最多有50个元素,并且最大的k值是10,那么选出的k个元素的最大和值就是50000*10,并且一定是整数,再根据上一段的分析,我们心里应该已经隐约有个想法了。定义一个二维数组selNums[i][j](i=1,2,...,k; j=1,2,...,50000*10),表示从n个数里选i个的和为j的组的个数,n从2开始逐渐增加到segments.size(),增加的过程中修改二维数组selNums的数据,修改的代码为selNums[ki][ mi+segments[i] ] += selNums[ki-1][mi],这条语句的意思可以从上一段的分析中得出。这样就可得到如下代码。

long long **selNums, ret;
int i, ki, mi, *sumsBefore, sum;
if( segments.size()==0 || k==0 ) return 0;
selNums = new long long * [k+1];		// selNums[i][j](i=1,2,...k, j=1,2,...,sum),为segments.size()个元素中和为j的i个元素
sumsBefore = new int[segments.size()];	// sumsBefore[i]为segments中下标i之前的元素和
assert( selNums && sumsBefore );
sort( segments.begin(),segments.end() );	// 排序的好处下面讲述
sumsBefore[0] = 0;
for( i=1;i<(int)segments.size();i++ ) sumsBefore[i] = sumsBefore[i-1]+segments[i-1];
sum = sumsBefore[segments.size()-1] + segments.back();
for( i=1;i<k;i++ )
{
selNums[i] = new long long [sum+1];
assert( selNums[i] );
memset( selNums[i], 0, (sum+1)*sizeof(long long) );
}
selNums[1][segments[0]] = 1;	// 按segments中元素从1到segments.size()个逐步迭代求selNums
ret = 0;
for( i=1;i<(int)segments.size();i++ )
{
// segments.size()中和大于segments[i]的k-1个元素才可能和segments[i]构成凸多边形。并且由于对segments是从
// 小到大的顺序处理的,所以只要前k-1个元素的和大于segments[i],则一定能构成凸多边形
for( mi=segments[i]+1;mi<=sumsBefore[i];mi++ )
ret += selNums[k-1][mi];
for( ki=k-1;ki>1;ki-- )
{
for( mi=0;mi<=sumsBefore[i];mi++ )
{
selNums[ki][ mi+segments[i] ] += selNums[ki-1][mi];
}
}
selNums[1][segments[i]] ++;
}
delete []sumsBefore;
for( i=1;i<k;i++ ) delete []selNums[i];
delete []selNums;
return ret;



但是测试后却发现,最大规模数据的运行时间还要4秒多,这不可以。经过分析可以得知,数组selNums的第二维不用弄那么大(我贴的代码中比分析中的值还要大)。我们最终需要的结果是能构成合法凸多边形的个数,所以n个数选m个(m小于k时)的和大于50000的时候,以后再加进来的边都可构成合法凸多边形(因为数据已经排过序,所以后加进来的肯定比以前加进来的大),那么就可以用selNums[i][50001]表示n条边选择i条的和大于5000的组数,这样就得到如下更快的代码。其实做点小修小补也许还能再快点。

const int longestEdge = 50000, maxK = 10;
static long long selNums[maxK][longestEdge+2];
long long ret = 0;
int i, ki, mi, tn;
sort(segments.begin(),segments.end());
memset( selNums, 0, sizeof(selNums) );
// 同样要迭代求解selNums,下面的赋值的意思是:从1条线段中选择一条长度为segments[0]线段的个数是1
selNums[1][segments[0]] = 1;
for( i=1;i<(int)segments.size();i++ )
{
tn = longestEdge+2;
for( mi=segments[i]+1;mi<tn;mi++ )	// 所有和segments能拼成凸多边形的情况,直接加到ret里
{
ret += selNums[k-1][mi];
}
for( ki=k-1;ki>1;ki-- )		// 按k从大到小的顺序更新selNums
{
tn = longestEdge - segments[i];
for( mi=0;mi<=tn;mi++ )	// 这个mi的范围再加上segments也不会超过longestEdge
{
selNums[ki][ mi+segments[i] ] += selNums[ki-1][mi];
}
// ki-1(这里ki<k)条线段的和再加上segment[i]已经大于longestEdge,以后再加入一些更长的(但是不会超过longestEdge)
// 的线段,一定不会违反凸多边形的限制
tn = longestEdge + 2;
for( ; mi<tn; mi++ )
{
selNums[ki][longestEdge+1] += selNums[ki-1][mi];
}
}
selNums[1][segments[i]] ++;
}
return ret;



可以到地址http://www.topcoder.com/stat?c=problem_solution&rm=301667&rd=13751&pm=9995&cr=22721553
看看牛人写的代码以及系统测试数据,那代码我是没怎么看懂,另外要有账号才能看。

Problem Statement

You are given N line segments numbered 1 to N. The lengths of these segments are given in the vector <int> segments. Compose a convex K-sided polygon, where each side is one of the given segments. Each segment can only be used once in the polygon. Return the number of different polygons you can compose. Two polygons are considered different if there exists a segment i such that one of the polygons contains segment i but the other polygon does not.

Definition

Class: Polygons2

Method: number

Parameters: vector <int>, int

Returns: long long

Method signature: long long number(vector <int> segments, int K)

(be sure your method is public)

Notes

A convex polygon can be constructed from a set of segments if the length of each segment from this set is strictly less than the sum of lengths of the remaining segments.

Constraints

segments will contain between 1 and 50 elements, inclusive.

K will be between 3 and 10, inclusive.

Each element of segments will be between 1 and 50,000, inclusive.

Examples

0)

{1,1,1,1}

3

Returns: 4

A nondegenerate triangle can be built using any triple from the given segments.

1)

{2,3,4,5}

3

Returns: 3

Any triple except {2,3,5} will do.

2)

{4,4,4,2,2,2}

3

Returns: 11

You can a make nondegenerate triangle using three segments of length 2, or three segments of length 4, or any two segments of length 4 with any segment of length 2.

3)

{10,1,4,9,20}

4

Returns: 2

One can build a convex quadrangle using segments {10,1,4,9} or {10,4,9,20}.

4)

{3310,1660,211,1260,160,213,884,539,17212,2025,105,120,5510}

7

Returns: 532
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: