<算法竞赛入门经典> 第8章 贪心+递归+分治总结

时间:2023-11-24 10:37:38

虽然都是算法基础,不过做了之后还是感觉有长进的,前期基础不打好后面学得很艰难的,现在才慢慢明白这个道理。

闲话少说,上VOJ上的专题训练吧:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=40741#overview

1. A UVA 10602 Editor Nottoobad

好像是俄罗斯NOI的题目,题意是给定n个字符串,然后重新安排字符串的顺序,使得最后需要打的字母总数最少。当前单词和前一个单词相同的前面部分可以不用打, 只需打后面的部分,并且第一个单词是要全部打出来的。

思路就是把所有输入分成两部分,即和第一个单词具有相同首字母的和其他剩下的单词。对剩下的单词进行排序,然后依次统计需要手打的字母。对于具有相同首字母的单词,需要统计和第一单词前面部分有多少个字母相同,然后按照相同字母个数由多到少排序。最后在具有相同的字母个数的单词内按照字典序排序。这样得到的就是最终最优序列了。

 

2. B UVA 10716 Evil Straw Warts Live

给定一个字符串判断能否通过左右交换字母使之成为回文串,并输出最少的操作数。

思路:能否构成回文串条件很简单:对于奇数长度的字符串,有且仅有一个字母出现次数为奇数个;对于偶数长度的字符串,所有字母出现次数必须为偶数。

关键部分在于最优交换策略:先从左右两边开始使之对应位置字符一样,这样每次选取一对分别移动到两端需要变化的位置总步数最少的字符。

这种方法只适用于字符串长度较小的情况,POJ上面一道题只是长度变为8000,然后发现这样做会超时,可是暂时没想到更好的方法了,难道是dp????先在这里留一个坑,等下个dp专题之后能不能解决。

 

3. C UVA 11100  The Trip, 2007

直接说解法好了,先从小到大排序,然后求出个数最多的那个数的数目,记为d,最后从头开始,以d为间隔遍历数组,直到数组全部遍历完。

 

4. D UVA 10245 The Closest Pair Problem

给定n个点的坐标,然后求出最近的两个点间的距离。

思路:典型的分治算法,首先分成两个部分,递归求解两个部分最近点对距离,记为d1, d2,记d = min(d1, d2) 然后就是两个部分分别有一个点时最近点对情况。

这里要用到鸽笼原理什么的,就是每次只需要从左右两部分,取距离中间点不超过d范围的那个点就行了。

 

5.  E UVA 11129 An antiarithmetic permutation

题意是给你0,1,2, 3, 4……n-1的序列输出一个排列使得其中任取三项都不会构成等差数列。

思路:分治算法,首先将整个序列分成两部分,左边部分即下标为偶数的部分,0, 2, 4, 右边部分为下标为奇数的部分,1, 3, 5, …,这样对于任何一个a,b,c只要它不完全位于左半部分或右半部分,

那么它就不可能构成等差数列。接下来只需处理完全位于左半部分或右半部分,使这样的数列也不为等差。重新对左右部分标号,然后同样方法递归处理。

中间有一个结论就是:对于一个相差d(d = 1, 2, 3, 4…)的连续的等差数列,比如本题一开始是0, 1, 2, 3,……n-1, 按照顺序给定下标,奇数下标和偶数下标分成两部分, 对于不在同一部分的三个数,a,b,c,是不可能构成等差的,因为同一部分两个数相差偶数个d,位于不同的部分的则相差奇数个d。

6.  F UVA 10041 Vito's Family

给定n个数,从中求一个数aj, 使得所有数与之相减绝对值之和:∑|ai-aj|最小。

排序后中间那个数就是要求的数aj了。

 

7. G UVA 507 Jill Rides Again

WA了好多遍,不知道是不是理解错误题意了,大体是求一个连续的子序列使得和最大,不过还有一些其他条件,反正不是很清楚它在讲些什么。

先留坑……

8.  H UVA 108 Maximum Sum

题意是求给定一个二维数组的和最大的子矩阵。

思路:转化成一维状态的最大子序列即可。

以每行为子矩阵的起始边界,枚举子矩阵的高度, 然后求出子矩阵每一列的和,得到一个数组a[1……n],然后求和最大的连续子序列。

可以用动态规划求解:dp[i]表示以a[i]结尾的子序列的最大和,转移方程为:dp[i] = max(dp[i-1], 0) + a[i].

9.  I UVA 10827 Maximum sum on a torus

与上面的题目区别在于这个二维数组是圆环上面的,意味着二维数组边界可以互相拼接一起构成一个子矩阵。

将二维数组扩充成2N * 2N即可,也就是4个原来的二维数组构成,按照上面的方法求子矩阵,注意子矩阵最大高度和宽度不能超过原来的数组。

 

10. J  UVA 757 Gone Fishing

有n个池塘,Joe从池塘出发依次经过0,1,2, n-1钓鱼,每个池塘每隔开始能钓到fi条鱼, 每隔5min减少di条,直到为0,从i-1到第i个池塘需要花费时间ti个5min的时间。

给定h(小时)求Joe最多能够钓到多少条鱼。

首先求出Time表示从0号池塘出发,到第i个池塘需要花费的时间。

然后对于h*12 > Time[i]的每个池塘i,th = h*12-Time[i], 求出0到第i个池塘间可以任意转移时, 花费th个5min能够钓到的最多的鱼。每个池塘建立一个结点

struct NODE {
int num;
int v;
NODE() {}
NODE(int v, int num):num(num), v(v) {}
bool operator < (const NODE &rhs)const {
int cmp = v - rhs.v;
if(!cmp) return num > rhs.num;
return cmp < 0;
}
};

建立优先队列,并让当前能够钓到鱼数目最多的池塘排在前面,同时序号最小排在前面。

每次取出头一个结点,作为钓鱼的池塘,知道th <= 0 或者头结点没有鱼可钓,最后将剩下的时间加到0号池塘停留的时间上。

 

11.  K UVA 10148 Advertisement

给定n个区间,每个区间要求至少覆盖k个点,不足k个点的区间则覆盖全部的点,求选取的最少的点数。

首先应该对所有区间排序,即按照:右端点从小到大排序,依次覆盖每个区间k个点,优先覆盖右端的点。

12.  L UVALive 2322 Wooden Sticks

 

13.  M UVALive 2326 Moving Tables

题目可以转换成给定n个区间,每次选取一系列不重合的区间,直到所有区间都被选取,最少需要选多少次。可以对每个区间进行统计,记录每个点被不同的区间包含的次数,包含最多的次数就是最后结果。

 

14.  N UVALive 2088 Entropy

哈夫曼树。

首先对应每个字符构建一个结点,然后按照频率排序,每次取两个频率最小的结点出来,并合并成一个新的结点,频率为两个频率之和,重复这个步骤,直到仅剩下一个结点。

具体可以使用优先队列,对NODE结点按照频率优先级排序,频率小的排在前面。

struct cmp {
bool operator ()(NODE *a, NODE *b) {
return a->freq > b->freq;
}
};

15.  O UVALive 2519 Radar Installation

x轴上方有n个点,要求在x轴放置半径为d的圆,求最少需要放置多少个这个的圆。

先判断每个点y值是否<= d,然后对每个点计算一个值,v = x-sqrt(d*d-y*y);也就是覆盖该点可能的圆心的最右位置。

然后按照这个值v从小到大排序,优先放置v最小的地方。扫描一遍即可。

 

16.

 

17.

 

18.

 

19. S UVALive 2911 Maximum

m个数满足:∑xi  = b*sqrt(a), 其中1<=i<=m, a, b 为整数,且a>0,xi介于-1.0/sqrt(a)~sqrt(a)之间。

求max(∑xi的p次方),p为一个正偶数。

为了使得最后结果最大,应该让每个|xi|得值尽量大,而且在和一定的情况下,xi应该往极端方向取值,尽量多的xi取得sqrt(a).

 

有下面的贪心算法:

依次选取x1……x2……xm但是每次要从和sum中选取满足条件的最大值,也就是初始时sum = b*sqrt(a),每次选取xi的值时,若sum - sqrt(a) >= –1.0/sqrt(a) , xi = sqrt(a);否则xi = –1.0/sqrt(a);

并且不断更新sum的值,注意最后一个值xm只能选sum。这样才能使得所有xi的和为sum且最后结果最大。

对于xi介于任意范围p~q, p < 0 && q > 0 ,且fabs(p) < fabs(q), 也会有这样的发现。

 

20. T UVALive 4062 You are around me ...

解决这个题目首先要知道,椭圆上与长轴夹角为th的点到椭圆中心的距离r满足的关系:r^2 = (a*b)^2/((asinth)^2 + (bcosth)^2).th为倾角.

然后由于题目中椭圆是有一定倾角的,所以要利用公式:x’= x*cos@+y*sin@;y’ = y*cos@-x*sin@.将椭圆旋转至水平状态。

利用公式可得ans = pi*a^2*sqrt(1-e^2)…………求得最小的长轴即可。也就是对于有两给点为中心的椭圆相切时椭圆长轴长度。

利用上面的公式,每次求出椭圆中心的距离以及两中心连线与长轴夹角之后便可以算出长半轴a。

然后利用分治算法就可以了,中间合并的时候同样只需要取两边距离不超过d的点,这个很容易证明,后面部分和最近点对分治算法基本一样。

感觉我也解释的不怎么清楚,具体也只能看代码了,可是我的代码感觉还是乱糟糟。