SGU 130 - 139 解题报告
131 Hardwood floor 动态规划:状态压缩
132 Another Chocolate maniac 动态规划:状态压缩
133 Border 简单统计
134 Centroid 简单统计
135 Drawing Lines 递推
136 Erasing Edges 数学:一次方程
137 Funny Strings 枚举
138 Games of chess 贪心
139 Help Needed! 数学:逆序数相关性质
130 Circle
卡特兰数
题意:给定一个圆,上面有2K个点,这2K个点可以互相连接,一个点只能连一次,问连接完毕将圆分割成最小的平面数的分割种类数。
题解:
1)首先K=1,最小平面为2,种数为f[1] = 1种。
2)然后考虑K=2的情况,将K0和K1连,那么剩下的情况就是K=1的情况,将K0和K3连,那么剩下的也是K=1的情况,所以K=2的情况为f[0]*f[1] + f[1]*f[0] = 2, 分割的平面数为3。
3)按照和K=2一样的递推方式,K = 5 时的情况为:
K0 - K1 -> f[0] * f[4]
K0 - K3 -> f[1] * f[3]
K0 - K5 -> f[2] * f[2]
K0 - K7 -> f[3] * f[1]
K0 - K9 -> f[4] * f[0]
于是可以得出递推式: f[i] = sum { f[j] * f[i-1-j] 0 <= j < i }
131 Hardwood floor
动态规划:状态压缩
方块A 方块B
图1
题意:利用以上两种积木(任意数量,可进行旋转),拼出一个M*N( 1<= M <= 9, 1 <= N <= 9)的矩形,问这样的方式有多少种。如M = 2, N = 3 的情况 ,有以下5种拼接方式:
图2
题解:考虑到N和M都很小,所以可以将每一行的状态压缩在一个二进制的整数中,0表示没有放置,1表示已经放置,图3的第一行状态就可以表示成101000(二进制表示,紫罗兰色表示上一行延伸过来的方块,灰色表示暂时没有放置方块的格子)。这样就可以通过枚举第i行的所有状态,扩展出第i+1行的所有状态。
举例说明:
如图3,枚举到第i行的状态为 101000(二进制数,实际操作的时候是转化成十进制然后通过位运算来变换状态的),假设对于当前状态的所有解的情况已经求出来,为DP[i][101000],然后可以通过枚举列将第i行填满。并且要记录下当前行状态now = 101000, 下一行状态 next = 000000。
那么接下来一步就要开始枚举列了,当枚举到第一列时,发现当前格子已经被i-1行所占据,所以直接进入下一列,这一列为空,所以我们需要某种方式将它填满,对于图3的情况,一共有三种填充方式(图3、图4、图5),因为格子不能重叠,所以在枚举的时候需要判断当前方块放入的时候会不会导致和之前放置的方块重叠,如果不会重叠说明状态可达,否则不进入下一次迭代。
now = 101000 next = 000000
图3
now = 111000 next = 110000
图4
now = 111000 next = 010000
图5
now = 111000 next = 011000
图6
图3的状态一共生成三种状态,然后我们继续对生成的状态进行扩展。以图5的状态为例,对它的状态进行扩展,可以得到以下几种状态(橙色表示当前放入的方块):
1)放置一个横躺的方块A
now = 111110 next = 010000
图7
2)放置一个竖着的方块A
now = 111100 next = 010100
图8
3)放置一个 "反7" 字 方块B
now = 111110 next = 010100
图9
4)放置一个 "L"字 方块B
now = 111100 next = 010110
图10
5)放置一个 "7" 字方块 B
now = 111110 next = 010010
图11
6)放置一个 "反L"字 方块 B
now = 111100 next = 011100
图12
枚举列的过程是用dfs递归实现的,递归出口为列数col == N的时候,那时候当前行的状态now一定是 2N - 1,因为要保证第i行能够被填充满,于是就可以得出状态转移方程DP[i+1][ next ] += DP[i][ now ]。
继续枚举第i+1行的所有状态,扩展i+2行的可达状态。
然后就要考虑初始状态的情况,可以虚拟出一个第0行,那么第0行的状态除了2N-1的情况为1,其他必定是0(因为是虚拟出来的,可以理解为全部填满的状态)。然后通过第0行的这个状态可以迭代计算出第1行的各种可达状态的解数。直到第M行,最后解的情况是DP[M+1][0]的值而非DP[M][2N-1](因为枚举到第M行后,第M行的非全满的状态也可以导致最后的格子铺满)。
132 Another Chocolate Maniac
动态规划:状态压缩
题意:给定一个M*N (M <= 70, N <= 7)的棋盘,求放置最少数量的多米诺骨牌(大小为2X1),使得它不能再放置更多的骨牌,给定的棋盘某些点不能被放置。
题解:沿用131那题的思路,但是这题只知道前一行的状态是不够的(因为多米诺骨牌的大小为2X1),考虑下面这种情况:
图1
现在需要推导第i+2行的状态,考虑在(i+2, 4)(红色方块)上如果不放置骨牌是否合法,那么由于(i+1, 4)为空,但是我们无法知道第i行的那个?格子到底是空还是非空,所以无法对红色格子进行状态转移,所以需要将状态表示稍作变形。
对于每一行用一个3进制的整数表示当前一行的状态,定义以下常量:
#define STATE_PRE_EMPTY 0 // 上一行对应格为空
#define STATE_PRE_FULL 1 // 上一行对应格为满
#define STATE_NOW_FULL 2 // 当前行对应格为满
那么图1的情况就可以表示成下面的状态:
图2
?格子所代表的的状态就可以通过i+1行的那个值来确定了,i+1行对应列的格子值为0,表示第i行对应列的格子为未放置状态(再上一行的状态就不需要关心了,因为骨牌的最大长度为2)。
那么如果前一行对应列的状态值为0的时候,必须放置一个骨牌(如果因为有障碍无法放置,那么说明这个状态无法往下转移了)。知道方法之后,具体做法就是初始化状态,这里我们为了运算简便,可以虚拟出一个第零行来,对于第0行,它一定是全部塞满的,所以第0行只有一个合法状态,即:DP[0][ 3N - 1 ] = 0(每格的状态都是2,最小骨牌数为0),然后通过这个状态,进行行枚举,每一行进行列位移(可以采用dfs实现),将上一行的状态推导产生下一行的可行状态,每次dfs模拟放置和不放置两种情况;
最后DP[M+1][ 3N - 1 ]就是最后的答案。
133 Border
简单统计
题意:给定N(N <= 16000)个整数对(Ai, Bi),如果存在一个整数对(A j, Bj) 使得 Aj < Ai 并且Bi < Bj 则称(Ai, Bi)是多余的,问整数对中有多少是多余的。
题解:将整数对按Ai递增排序,然后遍历Bi记录最大值,每次遇到比最大值小的可以断定一定是多余的,计数器加1。知道遍历完毕输出即可。
134 Centroid
枚举 + 树形统计
题意:给定一棵树,删除树上的某个结点(以及它所附带的边)i,剩下的各个连通块中结点数目最大值为Ti,求 M = Min{Ti 1<= i <= n},并且输出删除哪些点,能得到这个最小值M。
题解:遍历一棵树,并且用sum[i]记录以i为根结点的树的结点数目,通过一次遍历就能把所有的sum求出来。记录M = n;然后遍历根结点,对于删除i结点,剩下连通块中的最大值为 max( n - sum[v], max{sum[v] v为i个直接子结点} )。同样一次遍历就能将所有的值求出来。
135 Drawing Lines
只是奇怪不能写成 while (scanf("%d", &n) != EOF) 的形式,会PE。
136 Erasing Edges
数学题
题意:给定一个多边形S的N条边的中点,求这个多边形S。
题解:假设原多边形中点集合为Ci,多边形点集合Pi。
假设 第0个点P0 = (x, y),那么第一个点的坐标可以通过C0计算出来,即P1 = 2*C0 - P0,同理第二个点可以通过C1计算出,并且可以用P0的一次函数来表示,做n-1次迭代就可以把Pn-1用P0表示出来,而 Pn-1 + P0 = Cn-1,所以x,y都可以通过一次线性方程求出解来(也有可能有无解的情况)。然后再做n-1次迭代将所有点求出来即可。
137 Funny Strings
记忆化搜索
题意:对于一个序列S1, S2, S3 ... SN, 如果S1+1, S2, S3 ... SN-1进过一些左移或者右移几次操作,可以和原序列保持一致,则称这个序列为Funny String,给定N和K,求长度为N,和为K的Funny String。
题解:首先考虑,左移后第几位和原来的第一位对应,可以假设是第i位吧,那么有以下关系式:
S[1] + 1 = S[i] ... (1)
S[2] = S[i+1] ... (2)
S[3] = S[i+2] ... (3)
......
S[j] = S[1] ... (N-i+2)
S[j+1] = S[2] ... (N-i+3)
......
S[n-1] = S[i-2] ... (N-1)
S[n] - 1 = S[i-1] ... (N)
显然i不可能为1,因为S[1] + 1 != S[1]
并且GCD(N, (i-1)) 必须为1,否则这个等式组会存在内环,无法求解。
接下来就可以通过记忆化搜索将S[2] ... S[N] 的值都用S[1] 表示出来。
表示方式有很多种,例如用一个二元组Tk = (ak, bk)来保存对于前一项的系数,
例如:
S[i] 可以表示为 Ti = (1, 1) (即 S[i] = 1 * S[1] + 1)
S[i+1] 可以表示为 Ti+1 = (1, 0) (即 S[i+1] = 1 * S[2] + 0)
这里的S[2]尚未计算出来,所以需要递归计算,具体计算方法第j项的方法如下:
if j == i
return (1, 1)
else if j == i - 1
(ta, tb) = getS(n)
return (ta, tb - 1)
else if j > i
return getS( j - i + 1)
else if j < i
return getS(N + j - (i-1))
递归原因来自上面那N个等式,其中ta一定为1,tb为1或0。
所以这题的做法就是枚举左移点i,计算出所有数对于S[1]的一次线性表示形式,然后利用所有数之和为K这个条件,求出非负整数S[1],就能将所有数都求出来了。
138 Games of Chess
贪心
题意:给定N(N <= 100)个人的比赛场数X[i],需要构造一个比赛安排,假设G表示所有人的参赛场数之和,一共G/2场比赛,那么需要输出G/2行,每行为a b,a、b表示参赛队员的编号,其中a不等于b,并且a是胜利方,当前场的胜利方需要在上一场比赛中出现过(类似不断淘汰的机制)。
题解:将每个人的参赛场数X[i]从大到小排序,如图1所示,共有八位参赛选手,参赛场数分别为4、3、2、1、1、1、1、1,总和为14,所以总共有7场比赛。从第一个选手开始,将他的前X[i] - 1场比赛在赛程表的前X[i] - 1个位置的胜场中填满,他的最后一场比赛和场数次多的人比,并且输掉,将场数次多的人的剩余场数按照同样的方式去填赛程表,直到所有比赛的胜利方都确定(即表中左边那一列不为空),然后将剩下的还有剩余比赛的填在失败方即可。
图1
139 Help Needed!
数学证明
题意:如图,给定一个乱序的十六数码,问是否能通过一些移动,将它恢复到初始状态(0的位置表示空缺位置,非0位置上的方块可以向0移动,移动只能在上下左右四个方向进行)。
图1
题解:只需要判断 当前状态数码的逆序数 和 0到目标点曼哈顿距离 的奇偶性 即可。
证明如下:
首先将目标数码排成一排,计算它的逆序数为15(为了便于理解,将每一行的数码涂成不同的颜色)。
图2
然后来看任意状态的逆序数:
图3
I)如图3,考虑当前状态下的水平移动,即X可以向0移动,Y也可以向0移动(也可以理解成两个方块的交换)。对于这两种情况:
1) X 和0交换(X > 0),交换后的数码,逆序数-1;
2) Y 和0交换(Y > 0),交换后的数码,逆序数+1;
图4
II)如图4,考虑当前状态下的竖直移动,即A可以向0移动,B也可以向0移动。
由于两者情况是一样的,这里只介绍A和0交换的情况。
假设A和0中间的三个方块(十六数码,一定是三个方块)中比A小的有x个,那么比A大的就是(3-x)个,这三个方块的本身逆序数为K,那么:
1)交换前,A 到0 这个区间段的逆序数为 x + 1 + K + 3;
2)交换后,A 到0 这个区间段的逆序数为 (3-x) + K;
交换前后逆序数的差值为 (3-x) + K - (x + 1 + K + 3) = -2x - 1;
综合I 、II发现,每进行一次交换,逆序数的变化一定是奇数,最终状态的逆序数15也是奇数,所以如果某个状态逆序数和 0到目标点的曼哈顿距离 的奇偶性一致时,必定是无解的。