ccnu_2015级训练赛(1)题目解答

本文详细解答了CCNU_2015级训练赛(1)的六个问题,包括A到F题。涉及算法思路,如求和、枚举、数学分析、动态规划等,适合ACM竞赛选手学习。
摘要由CSDN通过智能技术生成

ccnu_2015级训练赛(1)题目解答

训练网页链接(后面每道题都有原题oj的链接)
http://acm.hust.edu.cn/vjudge/contest/view.action?cid=108635

problem A

http://codeforces.com/problemset/problem/4/A
大家都会做,不说了。

problem B

http://codeforces.com/problemset/problem/4/B
把每天的最小值求个和,记为sumLow,每天的最大值求个和是sumHigh。显然如果sumLow<=sumtime<=sumHigh就有解,否则就无解。
有解的情况只需要输出任意一组解,把sumtime – sumLow按照要求尽量分配即可。

problem C

http://codeforces.com/problemset/problem/484/B
数学题
通过读题可以发现每个数最大只是10^6。因而有一种想法是枚举除数。
比如一个除数是18。我们可以把被除数分为[0..17], [18..35], [36..48], … , [Max(18)..Max(18)+18],其中Max(x)表示小于10^6最大的整数。
显然,对于划分的每一段,最大出现的被除数 mod 18后是这一段所有出现的被除数中mod 18最大的,然后对所有划分段都求出最大的,然后再求个max。就可以得到除数是18的最大值。
枚举所有的除数,做同样的操作,就可以求出最大的ai mod aj(且ai>=aj)

可能初学者看上面有点懵比,我们下面来解决下可能会懵比的地方。
1 为什么这样划分后,只要找划分段中出现过的最大值。
仍然以18为例子。[0..17]中的数都有一个共同点: 它们整除18结果都是0,余数分别是0..17。[18..35] , [36..48], … , [Max(18)..Max(18)+18]都满足这个特点。因而每一段的数mod 18都具有单调性,因此在每一段中找最大的出现的数就是这一段数中mod 18最大的。

2 枚举除数会不会超时?时间复杂度是多少?
我们先看看枚举后会划分成多少段。
除数是1,划分的段数是10^6。
除数是2,划分的段数是10^6 /2。
除数是3,划分的段数是10^6 /3。
除数是4,划分的段数是10^6 /4。
除数是5,划分的段数是10^6 /5。

除数是10^6,划分的段数是10^6 /10^6=1。
数学上有个结论:1/1+1/2+1/3+1/4+1/5+…+1/M约等于ln(M)
所以总共划分的段数是10^6*ln(10^6),约为2*10^7。其实除数最多是2*10^5,所以真正的段数比这个估计要小2-3倍数。
显然枚举除数划分是不超时的。假如我们能O(1)找出每一段的最大值,就可以用总划分段数的时间复杂度来完成这题。

3 怎么O(1)找出每一段的最大值。
这个我们需要做一个预处理。开一个2*10^6的桶,对于读入的n个数,用桶存起来。什么是桶呢。就是一个以数字大小为下标的数据结构(其实就是一个数组)。

0123456789
0001001101

上面的表格,第一行是数组下标,第二行不是0就是1,其中0表示这个数读入数据里没有,1表示这个数读入数据里有。这个桶的意思就是有4个数,分别是3、6、7、9。不妨把这个桶记为数组bkt[]。
接下来,再开一个同样大小的桶,记为pre[]。pre[i]表示前bkt[0..i]中为1的最大的数是几。

0123456789
-1-1-13336779

-1表示前面没有数字,其它数表示小于等于i的最大的数。读入的时候先求出bkt[],然后从前往后扫一遍求出pre[]。
这两个桶预处理好后,对于每一个划分段落[x..y],如果pre[y]< x,表示[x..y]中一个数都没有出现,否则pre[y]就是[x..y]中最大的出现的数。

至此,如果你弄懂了并且细节处理好,这道题就可以AC了。

problem D

http://codeforces.com/problemset/problem/487/A
题目大意:
主角和怪都有三个数值HP,ATK和DEF。每回合,主角对怪对伤害是max(0,主人公的ATK - 怪的DEF),怪对主角的伤害则反过来。每回合怪和主角同时受到伤害,当怪的HP<=0,主角的HP>0时,主角胜利。
现在已经知道主角和怪的初始HP,ATK和DEF。主角为了胜利,可以通过花钱提升自己的三个数值,每提升一点分别需要花费h,a和d。问主角胜利的最小花费是多少?
读入数据全部是[1..100]间的整数。

解答
枚举。
显然主角提升后的ATK不会超过200,DEF不会超过100,所以我们可以两重循环枚举主角最终的ATK和DEF,然后算出主角胜利最少需要的血量,与初始血量取max,算出这种情况下的花费,所有情况中取一个花费最小的就是答案。

problem E

http://codeforces.com/problemset/problem/4/D
以wi为第一关键字,hi为第二关键字从小到大sort。(排序要多记个读入的顺序)
排序DP后,记状态F[i]是第i个信封结尾和前i-1个信封结合在一起的最多信封数量。如果第i个信封不能装入卡片,则可以令F[i]=-1表示该状态不合法。否则可以在前i-1信封中找出比i严格小的信封j,而且F[j]最大。状态转移方程是F[i]=max(F[j])+1(其中1<=j< i且信封i严格小于卡片,信封i严格大于信封j)
输出方案开个数组pre[]记录转移路径(即pre[i]=j,j是最优转移状态),最后扫一遍F[]找出一个最大值F[x],沿着pre[x]把路径找出来,输出即可。(对于F[i]=1,可以使pre[i]=0)

int tt=0;
for (; x!=0; x=pre[x]) o[++tt]=x;

上面的代码就是把转移路径找出来,存在o[],最后倒着输出相应的读入顺序就可以了。

problem F

http://codeforces.com/problemset/problem/4/C
这道题,其实就是在线查找相同字符串出现次数的问题。一个简单的做法是一边记一边找,每次读入读入第i个字符串,把他记在数组的第i位,然后扫一遍1到i-1位有多少个字符串和第i个相同,统计一下即可。然而这样做的时间复杂度是O(n^2),n是10^5级别的,显然不能在规定时间内求解。
因而我们要借助一些特别的数据结构,或者使用一些技巧处理这个问题。
数据结构有:hash表,map,字母树……
技巧有:离线有序化

我们下面各讲一个。

map

在搜索引擎里输入关键字map c++,都可以找到相应的map操作。大概简单介绍一下,map是用红黑树(一种稳定二叉平衡树)来实现的STL数据结构。我们可以直接引用,include < map> 把它当成一种类型使用。
map < string,int> Map; 前面map < string,int>相当于int,是一个类型, 后面的Map是变量名。map< A,B>类型是一个二元组类型,其中A,B也都是类型,但表示意义不同。A表示key, B表示value。
key就是关键字,map< key,value>这个类型的大小关系取决于key,value是一个值。在这道题里面,key就是出现过的字符串,value就是该字符串出现的次数。字符串用字符串类型string存储(string也是STL中的一种,也得include < string>),次数用整数类型int存储,所以我们的map类型就是map< string,int> 。
map支持的操作有insert,erase,find,也可以直接Map[key]访问关键字的value,这些可以自己去查怎么使用。学会之后,用map AC这题非常简单。

离线有序化

我们先把所有字符串读入,但不进行统计,把他询问都存在一个数组Que []里。另开一个数组S[],把字符串copy到S[]里,对它sort,也就是有序化。(这里的类型都可以直接用string)
sort之后,S[]是单调的,而且每一个询问Que[]里的字符串都在S[]里面出现,我们另开一个类型是int的数组V[]来统计询问到第i个字符串的时候,该串出现了多少次。
从1到n枚举询问,对于每个Que[i]我们可以二分查找出它在S[]的位置,假设是k,那么该字符串出现的次数就是V[k],同时另V[k]++修改即可。 (lower_bound是c++提供的二分,搜一下资料,学会了可以直接用)
先把询问存起来,把有用信息排序后,再对询问处理,这种技巧是常用的离线技巧。

小结

以上两种方法的时间复杂度是O(n*log(n)*32),为什么要乘32呢,因为字符串的比较是逐位比较的,最坏情况下是比较32次。(读入字符串长度均不超过32)

编写人:
勿蓄樊中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值