题目大意:n个有颜色的补丁,你可以随时将某种颜色的布丁变成另外一种颜色,请问操作过程中分别有多少段颜色?
题目描述
N个布丁摆成一行,进行M次操作.每次将某个颜色的布丁全部变成另一种颜色的,然后再询问当前一共有多少段颜色.例如颜色分别为1,2,2,1的四个布丁一共有3段颜色.
输入输出格式
输入格式:
第一行给出N,M表示布丁的个数和好友的操作次数. 第二行N个数A1,A2…An表示第i个布丁的颜色从第三行起有M行,对于每个操作,若第一个数字是1表示要对颜色进行改变,其后的两个整数X,Y表示将所有颜色为X的变为Y,X可能等于Y. 若第一个数字为2表示要进行询问当前有多少段颜色,这时你应该输出一个整数. 0
输出格式:
针对第二类操作即询问,依次输出当前有多少段颜色.
输入输出样例
输入样例#1:
4 3 1 2 2 1 2 1 2 1 2
输出样例#1:
3 1
说明
1<=n,m<=100,000; 0<Ai,x,y<1,000,000
解题思路
暴力做法:对于每次更改颜色x为y,可以枚举n个布丁,如果布丁颜色是x那么将其颜色改成y;对于询问有多少段颜色,同样枚举n个布丁,如果发现当前布丁与前一个布丁颜色不同,累加颜色段数;时间复杂度是O(nm),结果是超时。
如何优化呢?m次操作,O(m)无法优化;对于更改颜色方面,极端情况是n个布丁都需要更改颜色,但如果我们更改颜色时,选颜色布丁少的那种颜色来变,并开一个数组记录每种颜色对应的编号,这样修改次数就大大减少了!当然,复杂度没有变化,因为还需要遍历n个布丁。如果用链表就只需要遍历该颜色的布丁而已,复杂度就可以降低到log级别了,这就是链表的启发式合并。
先说合并,如5个布丁,颜色分别是1 2 2 2 2,操作是把颜色2改成颜色1,如果直接操作,需要改4次颜色,最终变成1 1 1 1 1,但如果把1改成2才需要改1次颜色,最终变成2 2 2 2 2。如果后面又要把颜色1改成颜色2,前者可以继续改,后者就找不到1了,但实际上颜色全是1,怎么办呢?用一个数组记录每一种颜色对应的编号!颜色编号分别是1 2 2 2 2,操作是把颜色2改成颜色1,最终变成2 2 2 2 2时,同时需要更新颜色1的编号是2,颜色2的编号是1。若下次要把颜色1改成颜色2,那么颜色1的编号改成颜色2的编号还是可以继续操作的(将颜色编号数组中的2变成1)。
每次把颜色少的合并到颜色多的,多次合并后,每次合并均摊复杂度是log级别的,为什么呢?
因为每一种颜色,只要变为另外一种颜色,那么他所在集合就至少扩大一倍(另外一种颜色不比他少);也就是说,对于每一个元素,更改颜色次数不会超过log(n),如果超过了,那么总数必超过n!
对于查询颜色段数,可以在合并过程中顺便处理好:如果改布丁修改颜色后,根旁边的布丁颜色一样,那么颜色段数减少1。注意,旁边包括左边和右边,需要分开计算。处理好段数后,再进行修改,修改完后,合并两个链表,可以用这种形式:h[x] … w[x] → h[y],然后h[y] = h[x],颜色个数只需要累加和清零就行。
整体时间复杂度:O(m*log(n))
程序实现
线段树合并,自动“启发式”,遇到一个子树为空整棵树O(1)合并,效率更高。因为一共n个位置,每个位置至多被合并1次,每次一条路log,总的复杂度O(nlogn)。