题1:ATM Queue
题意
N个人排队取钱(初始顺序1,2,3……N),第i个人想取Ai元,但是ATM机每次只能取X元。如果有人要取的钱多于X元,就需要到队尾重新排队,直到取完离开。求这N个人取完钱离开队伍的次序。
数据范围:
小数据:所有数字在100以内
大数据:
N<=105
Ai<=109
X<=109
分析
笨办法是模拟,但对于大数据是要超时的,因此要避免模拟、直接算出这些人的顺序。
对于第i个人,他需要取A[i]/X(上取整)
轮。第一轮的人一定比第二轮的人先走完,而同一轮的人中编号小的人会先离开(他在每一轮中都会排在编号大的人的前面)。
于是我们可以预处理出每个人的结束轮次号,可用(轮次号,人编号)
这个数对来表示,然后从小到大排序(先按轮次号从小到大,再按人编号从小到大),最后依次输出数对中的人编号即可。
题2:Metal Harvest
题意
数轴上有N个不重叠区间,问最少可以用多少个长度为K的线段实现全部覆盖。
数据给出N个区间的起点Si、终点Ei,和K值,输出最少需要线段的个数。
数据范围
N<=109
K<=109
1<=S,E<=109
解法
贪心。对区间从左到右排序,最开始从第一个区间的左端点处开始放线段,直到覆盖第一个区间(线段个数可用上取整除法算出),然后再看上个线段是否覆盖了第二个区间,如果完全没有的话就再从第二个区间的左端点放,如果部分覆盖的话就从线段右端点开始放,直到覆盖第二个区间……非常简单。
题3:Painters’ Duel
题意
有一个三角形网格结构的博物馆,边长为S(图中是3),每个网格是一个房间。A同学和B同学分别在其中两个不同的房间。他们在玩一个比谁粉刷房间最多的游戏,游戏规则是:
- A和B轮流粉刷自己所在的房间(A先B后)。最开始A、B所在的房间都已粉刷,第一轮不需要刷。
- 每轮粉刷后,A和B需要移动至一间相邻的、未粉刷过的、且没有损坏的房间(损坏的房间列表由数据给出)
- 如果A或B无法移动,该同学的游戏结束,做轮空处理。
- 如果A和B都无法移动,游戏结束。
- 最后游戏的得分是:A粉刷的房间数-B粉刷的房间数。
A和B都采用最优策略试图赢得游戏:A尽力让得分最高,B尽力让得分最低。
数据给出A、B初始位置和损坏房间列表,请算出A一定能获得的最高得分(无论B怎么玩,A都能得到的最高得分)。
样例数据
格式
第1行:测试数据个数
每个数据第1行:S,A起始位置(行、列),B起始位置(行、列),损坏房间个数C
每个数据后C行:损坏房间位置列表
样例1
2
2 1 1 2 1 0
2 2 2 1 1 2
2 1
2 3
答案:2,0
样例2
2
3 3 4 2 1 2
2 3
3 1
3 3 2 2 3 2
2 1
3 1
答案:0,-1
数据范围
小数据:边长S<=2
大数据:边长S<=6
分析
一定要理解好什么是“两个人都采取最优策略”、“一定能得到的最高分”、“无论B做什么,A都能得到”。
刚开始我不理解,又看错了游戏规则(误以为B要让自己的分数最少),以为两个人会一个找最长可行路径、另一个会找最短可行路径。然后看清楚了游戏规则,想到枚举两个人轮流走的所有可能路径,取最高分,但想想觉得会超时。之后又在想直接算出理想最高分,想到动态规划、图论、二分……
实际上,这几句话的正确理解是,包含以下几层意思(觉得难懂可以直接看例子):
- A推演了所有未来情况(A怎么走,B怎么走,A再怎么走,B再怎么走……)
- 一定能得到的分数是指:A在考虑了B的所有选择(包括B用最优策略的情况),也就是“无论B做什么”,B玩得特别好也好,B玩得特别烂也罢,A总能达到的一个确定的分数(实际上是B表现的特别好时的分数)。
- 一定能得到的最高分是指:通过最优策略使这个分数提到最高,的那个分数。
- A能预知到自己每种走法能一定得到的得分,然后选择其中分数最高的走法,作为自己的最优策略。每一轮都如此,每一轮A都能预知到该轮自己的各种走法(通过模拟B的所有相应走法,再模拟自己的走法……)直到游戏结束时能一定得到的最高分,从而决定自己当前轮该怎么走。
- 这些道理对B也是一样的。B也推演了未来所有情况,知道自己当下做哪种选择一定能得到的分数最低,会按照这个最优策略进行游戏。
- 每轮A推演的情况中已包含了B所推演的情况,也就知道B下一步会做的选择,从而A会做出当前最优的选择。
简单说最优策略就是:
- 当有多种选择,并预知每种选择的分值,选择分值最优的。
- A的最优策略:推演未来所有情况,预判每种走法一定能得到的分数,选择分数最高的走法。
- B的最优策略:推演未来所有情况,预判每种走法一定能得到的分数,选择分数最低的走法。
- 要计算一定能得到的分数,需要递归地推演接下来的游戏过程,注意回溯时需要应用最佳策略。
再解释一下(可以不看):
- 一定能得到的最高分不是未来所有情况中的最高分(只有B一心按最短的死路走,A才能得到这个分数),只要B认真玩,A就不可能。
- 能一定得到的最高分不一定是实际的最后分数,实际分数仍取决于B怎么走,只是保证一定不比这个差。不过题目要求两个人都采取最优策略,A的走法和B的走法就全部固定了(游戏过程全部固定),最后的实际得分就是这个分数。
- 就算A有先手优势,A的最优策略不代表A能逼着B做某种选择,B的选择权仍然在,B可以是个很好的玩家,做出很好的选择。A的最优策略只能(通过自己的选择)限制B的未来选择,比如从B的最好的4种未来中,让其中最好的2种变得不可能。
举例,在某轮中,在A的第一种走法选择下(如A向左走一步),预计到B的相应两种选择(如B向右走一步或向上走一步)所产生的一定得到的得分是6、12,A的该选择的保证一定得分就是6(意思是不管B做什么选择,A能保证得到6分,6也是B做最佳选择的情况,记住B要让分数最低)。
如果该轮A共有三种走法选择,每种选择的保证得分是6、-2、7,A的该轮保证最高得分就是7(A会选第三种,从而让分数最高)。
(例子中B的两种选择的保证最高得分是通过推演下一轮(及之后)A的选择算出的,要递归算)
为了推演未来所有的情况,算法就是枚举A和B的所有游戏状态,也就是DFS出两者的所有可能路径(交叉走)。
这题的解法关键是:推演所有情况+回溯时做出最优决策。
代码
核心伪代码:
state[pos]全局变量:表示每个房间的当前状态,free或者blocked,free表示可以进入,blocked表示不可进入(已粉刷或损坏)
dfs函数:求从当前游戏状态算起的保证最高得分,游戏状态用地图的状态(同上)、a的位置、b的位置三个变量来表示
dfs(apos, bpos):
score = max{
for each possible(not blocked) next position of A {
state[nextapos] = blocked
= min{
for each possible next position of B {
state[nextbpos] = blocked
= dfs(nextapos, nextbpos)
state[nextbpos] = free
}
else // B game over if impossible to move
{
= 1 + dfs(nextapos, b)
}
}
state[nextapos] = free
}
else // A game over
{
= min{
for each possible next position of B {
state[nextbpos] = blocked
= -1 + dfs(apos, nextbpos)
state[nextbpos] = free
}
else // A,B game over
{
return 0
}
}
}
}
answer = dfs(astartpos, bstartpos)
实际上不会超时,得感谢出题人精确地控制了边长范围,三角形网格的结构也使得路径数不太多。
延伸:用类似方法判断抢30游戏是否有必胜策略
再来个经典抢30的案例,帮助加深下对博弈论题目的经典编程套路的理解。
抢30的规则大家都清楚,A、B轮流取1~3个数,先抢到30为胜。必胜策略大家应该也都知道,先手必胜(且第一轮需抢到2)。
现在我们要用程序判定A是否有先手必胜策略。为了增强问题普适性,不妨把目标30改为X,变成“抢X游戏”,但每轮每人依然最多只能3个数。
算法是:用f(i)表示上一轮B抢到i时,A是否有必胜策略(f(0)为初始状态,表示A是否有先手必胜策略)。
判定某状态有必胜策略的条件为以下之一:
- A做出某种选择可以直接抢到X;
- A做出某种选择时,无论B选择1到3中的哪个数,A接下来都能赢。
以上条件可表示为
i+1 <= X <= i+3
- 对于某个nexta(即
i+1
或i+2
或i+3
),(f(nexta+1) and f(nexta+2) and f(nexta+3)) == true
(表示B不管做出3种选择中的哪一种,A都能赢)。
边界条件是f(X)=false
(表示B抢到了X,A输了),f(X+1)=f(X+2)=f(X+3)=true
程序结构与上文类似,也是DFS枚举两人的选择,很好写
def f(lastb):
if lastb == X:
return False
elif lastb > X: # (1)
return True
# for each next position of A
for nexta in [lastb+1, lastb+2, lastb+3]:
if nexta == X: # (2)
return True
# for each next position of B
if f(nexta+1)