多重背包单调队列优化思路_动态规划入门——多重背包与单调优化
本文始發(fā)于個(gè)人公眾號(hào):TechFlow,原創(chuàng)不易,求個(gè)關(guān)注
今天是算法與數(shù)據(jù)結(jié)構(gòu)的第14篇文章,也是動(dòng)態(tài)規(guī)劃專題的第三篇。
在之前的文章當(dāng)中,我們介紹了多重背包的二進(jìn)制拆分的解法。在大多數(shù)情況下,這種解法已經(jīng)足夠了,但是如果碰到極端的出題人可能還是會(huì)被卡時(shí)間。這個(gè)時(shí)候只能用更加快速的方法,也就是今天我們要一起來(lái)看的單調(diào)優(yōu)化。
單調(diào)優(yōu)化是單調(diào)隊(duì)列優(yōu)化的簡(jiǎn)稱,單調(diào)棧我們?cè)谥暗腖eetCode專題已經(jīng)介紹過(guò)了。它的本質(zhì)只是一個(gè)簡(jiǎn)單的棧,通過(guò)在插入元素時(shí)候?qū)m數(shù)牟糠衷剡M(jìn)行彈出,從而保證了棧內(nèi)元素的有序。而單調(diào)隊(duì)列也類似,只是插入元素的位置不同而已。棧是只能從棧頂插入,隊(duì)列則是從隊(duì)尾插入。
比如我們看下下圖,下圖就是一個(gè)典型的單調(diào)隊(duì)列,隊(duì)列中的元素是[10, 6, 3]。隊(duì)首是10,隊(duì)列當(dāng)中的元素從隊(duì)首開(kāi)始往隊(duì)尾遞減。
如果這個(gè)時(shí)候我們從隊(duì)尾插入9,由于9大于隊(duì)尾的3,所以3出隊(duì)列,我們繼續(xù)判斷,發(fā)現(xiàn)9依然大于6,所以6再次出隊(duì)列。最后得到的結(jié)果是[10, 9]。準(zhǔn)確的說(shuō),由于我們進(jìn)出隊(duì)列的操作可以同時(shí)在隊(duì)首或者隊(duì)尾進(jìn)行,所以嚴(yán)格說(shuō)起來(lái)這并不是普通的隊(duì)列,而是一個(gè)雙端隊(duì)列。
單調(diào)隊(duì)列或者說(shuō)單調(diào)棧的最大用處是由于容器內(nèi)元素遞增或者遞減,所以棧頂或者是隊(duì)首的元素就是最值。我們通過(guò)使用單調(diào)棧可以在常數(shù)時(shí)間內(nèi)獲得某一個(gè)區(qū)間內(nèi)的若干個(gè)最值,在一些問(wèn)題當(dāng)中只獲得一次最值還是不夠的。因?yàn)榉N種條件的限制,所以可能使得最值不一定能夠成立,這個(gè)時(shí)候需要求第二最值或者是第三最值,在這種問(wèn)題下, 使用單調(diào)棧或者是單調(diào)隊(duì)列就是非常有必要的了。
比如今天要討論的多重背包問(wèn)題,就是這樣的情況。
基礎(chǔ)分析
我們先把單調(diào)隊(duì)列的事情先放在一邊,先來(lái)仔細(xì)分析一下題目。
在之前的文章當(dāng)中,我們?cè)?jīng)討論過(guò)動(dòng)態(tài)規(guī)劃算法的復(fù)雜度問(wèn)題。對(duì)于動(dòng)態(tài)規(guī)劃算法而言,我們要做的是遍歷所有的決策,以及決策可以應(yīng)用的狀態(tài),找到每個(gè)狀態(tài)最佳的轉(zhuǎn)移,記錄這些最好的轉(zhuǎn)移結(jié)果。在所有的結(jié)果當(dāng)中的最值,就是我們要找的整個(gè)問(wèn)題的答案。那么,我們可以很方便地推導(dǎo)出動(dòng)態(tài)規(guī)劃的復(fù)雜度,等于狀態(tài)數(shù)乘上決策數(shù)。
我們記住這個(gè)簡(jiǎn)單的結(jié)論,它可以幫助我們很方便地分析動(dòng)態(tài)規(guī)劃算法的復(fù)雜度,尤其在一些通過(guò)傳統(tǒng)方法不方便分析的時(shí)候。
我們把復(fù)雜度結(jié)論帶入多重背包問(wèn)題,對(duì)于多重背包問(wèn)題來(lái)說(shuō),我們的決策是由兩個(gè)條件決定的。其中一個(gè)是物品,另一個(gè)是這個(gè)物品的數(shù)量。所以決策的數(shù)量等于物品數(shù)乘上物品的個(gè)數(shù),狀態(tài)是背包的容量。我們假設(shè)物品的數(shù)量是N,物品的個(gè)數(shù)為M,背包的容量是V,那么它的復(fù)雜度就是
。顯然,在絕大多數(shù)情況下,這個(gè)復(fù)雜度是我們不能接受的,也是我們需要引入種種優(yōu)化的原因。在之前的文章當(dāng)中,我們通過(guò)二進(jìn)制表示法,將物品的數(shù)量拆分成了若干個(gè)2次冪的和。所以對(duì)于二進(jìn)制表示法而言,它的復(fù)雜度是
。我們通過(guò)二進(jìn)制表示將M這一維降到了,那么有沒(méi)有辦法將它繼續(xù)簡(jiǎn)化呢?當(dāng)然是有的,我們繼續(xù)來(lái)分析。在NVM這三個(gè)維度當(dāng)中,N是無(wú)論如何不能減少的。我們有N種物品再怎么樣也得比較一下這N個(gè)物品的好壞優(yōu)劣,不可能說(shuō)還有策略都不考慮就得到最優(yōu)結(jié)果,這是不可能的。既然N這一維度不能動(dòng),我們只能從VM這兩個(gè)維度入手了。
這個(gè)M比較討厭,我們能不能想一個(gè)辦法來(lái)解決掉它呢?
在樸素的想法當(dāng)中,我們需要遍歷拿取的個(gè)數(shù)來(lái)找到最優(yōu)的子結(jié)構(gòu)。比如當(dāng)前的狀態(tài)是i,我們需要遍歷0-M中所有的j,看看究竟dp[i]的最優(yōu)解是通過(guò)哪一個(gè)j轉(zhuǎn)移得到的。那么有沒(méi)有辦法,我們不用枚舉,自動(dòng)可以獲取呢?
當(dāng)然是有的,這個(gè)也是單調(diào)隊(duì)列優(yōu)化的精髓。
單調(diào)隊(duì)列
下面,我們來(lái)一點(diǎn)一點(diǎn)推導(dǎo)單調(diào)優(yōu)化的過(guò)程。
首先,我們假設(shè)當(dāng)前遍歷到的狀態(tài)是i,也就是背包容量是i,當(dāng)前的物品是item,它的體積是v,價(jià)值是p,數(shù)量是n。我們先來(lái)看第一個(gè)洞見(jiàn),對(duì)于狀態(tài)i而言,它只能從i-kv狀態(tài)轉(zhuǎn)移得到。這里的k是一個(gè)[0, min(i / v, n)]范圍內(nèi)的整數(shù),min(i / v, n)這個(gè)式子我想應(yīng)該大家都能看懂,就是當(dāng)前狀態(tài)i最多能夠包含多少個(gè)物品item。這個(gè)數(shù)量是物品數(shù)量的上限n和i這個(gè)狀態(tài)最多裝得下的數(shù)量i / v中較小的那個(gè),我們令min(i / v, n)這個(gè)值叫做cnt。
也就是說(shuō)對(duì)于狀態(tài)i而言,它最多包含cnt個(gè)item,最少包含0個(gè)。那么它的轉(zhuǎn)移可能性一共只有cnt種,而不可能從i-1以及i-2等其他狀態(tài)轉(zhuǎn)移到。我們寫(xiě)出這個(gè)狀態(tài)轉(zhuǎn)移方程,可以得到:
也就是說(shuō)在當(dāng)前item也就是當(dāng)前決策下,狀態(tài)i的最好結(jié)果一定是由一系列確定的子狀態(tài)其中之一轉(zhuǎn)移得到的,并且這些子狀態(tài)和一個(gè)整數(shù)k掛鉤,k的取值范圍是[0, cnt]。我們先暫時(shí)忽略這個(gè)范圍,簡(jiǎn)化問(wèn)題。考慮最極端的情況,最極端的情況這個(gè)物品數(shù)量管夠,在這種情況下,我們可以列一下可以通過(guò)item轉(zhuǎn)移到i的所有狀態(tài),它是一個(gè)序列:[i % v, i % v + v, i % v + 2v, ..., i - v]。
在之前裸的做法當(dāng)中,我們是通過(guò)一重循環(huán)來(lái)遍歷了這個(gè)序列,從其中找到了最佳的轉(zhuǎn)移。我們現(xiàn)在希望可以不用遍歷,通過(guò)某種方法快速找到其中最優(yōu)的狀態(tài)進(jìn)行轉(zhuǎn)移。這個(gè)邏輯應(yīng)該不難理解,到這里,我們沒(méi)有引入任何花哨的操作。
我們下面來(lái)做一點(diǎn)簡(jiǎn)單的分析,我們已經(jīng)列舉出了對(duì)于狀態(tài)i所有可能轉(zhuǎn)移到的上游狀態(tài)。我們不希望通過(guò)遍歷來(lái)找到其中最佳的轉(zhuǎn)移,順著這個(gè)思路,我們大概可以猜測(cè)一下,應(yīng)該通過(guò)某種方法找到這個(gè)序列當(dāng)中的某個(gè)最值。只有這樣,我們才可以不需要遍歷,快速找到答案。但是通過(guò)什么方法,尋找什么最值我們現(xiàn)在還不知道。到這里,我們又往前進(jìn)了一步,大概知道了接下來(lái)的策略,但是具體的細(xì)節(jié)我們還不知道,沒(méi)關(guān)系,我們先放一放,繼續(xù)進(jìn)行分析。
為了簡(jiǎn)化書(shū)寫(xiě),我們令 m = i % v,也就是當(dāng)前狀態(tài)對(duì)物品item體積的余數(shù)。那么上面的那個(gè)序列可以寫(xiě)成[m, m+v, m+2v, ... i - v]。由于在本問(wèn)題當(dāng)中,我們希望背包里的價(jià)值越大越好,所以顯然對(duì)于dp[m], dp[m+v], dp[m+2v]... 這個(gè)序列而言,它是遞增的。原因也很簡(jiǎn)單,對(duì)于每一個(gè)狀態(tài)而言,dp數(shù)組當(dāng)中都存儲(chǔ)的它對(duì)應(yīng)的最優(yōu)結(jié)果。所以不可能出現(xiàn)我們用了更多的空間,但是背包里的價(jià)值卻減少的情況。
我們當(dāng)然希望可以簡(jiǎn)單地從dp[m], dp[m+v], dp[m+2v]...dp[i-v]這個(gè)序列當(dāng)中選取最大的那個(gè),但是由于上面這個(gè)結(jié)論,所以我們并不能這么做。不能這么做的原因有兩個(gè),一個(gè)剛才說(shuō)了,因?yàn)閐p[i]是一個(gè)隨著i遞增而遞增的序列,背包裝的東西越多,裝的價(jià)值只會(huì)越大,不會(huì)減少。還有一個(gè)原因是后效性,這個(gè)問(wèn)題和零一背包的情況有些相似。舉個(gè)例子,比如說(shuō)dp[m] = x,如果dp[m+v]=x+p,也就是說(shuō)dp[m+v]由dp[m]轉(zhuǎn)移得到,代表它已經(jīng)裝了一個(gè)物品item。如果我們?cè)購(gòu)膁p[m+v]進(jìn)行轉(zhuǎn)移,我們則無(wú)法判斷到底一共拿取了多少個(gè)物品,也就無(wú)法判斷是否違法。
這兩個(gè)問(wèn)題,我們一個(gè)一個(gè)來(lái)解決,先說(shuō)第二個(gè)問(wèn)題。這個(gè)問(wèn)題解決的方法很多,最簡(jiǎn)單的就是將這個(gè)序列的結(jié)果單獨(dú)存儲(chǔ)一份,使得當(dāng)我們更新dp的時(shí)候不會(huì)影響。在零一背包當(dāng)中我們通過(guò)倒敘遍歷來(lái)解決了這個(gè)問(wèn)題,但是在多重背包當(dāng)中,這種方法不太適用。接著我們來(lái)看第一個(gè)問(wèn)題,我們直接找到序列最值不可行的原因是因?yàn)楸嘲萘恳鹆瞬还?#xff0c;為了解決問(wèn)題,我們需要想辦法消除這種不公。消除的辦法也簡(jiǎn)單,我們可以通過(guò)某種方法,將這些值放到同一個(gè)基準(zhǔn),消除因?yàn)槿萘孔兓鸬牟还?/p>
實(shí)際上這個(gè)基準(zhǔn)很好找,就是m。我們假設(shè)dp[m], dp[m+v], dp[m+2v]...dp[i-v]這個(gè)序列當(dāng)中的值都是通過(guò)dp[m]轉(zhuǎn)移得到的。比如dp[m+v],如果是從dp[m]轉(zhuǎn)移得到的,那么dp[m+v]應(yīng)該等于dp[m]+p。同理,dp[m+2v]應(yīng)該等于dp[m]+2p。這里需要注意,這里的數(shù)據(jù)都是沒(méi)有經(jīng)過(guò)item物品更新過(guò)的結(jié)果,是上一個(gè)物品更新之后得到的值。所以這里的dp[m+v]一定不是通過(guò)dp[m]轉(zhuǎn)移得到的,如果dp[m+v] - p > dp[m],那么顯然可以說(shuō)明dp[m+v]的潛力要比dp[m]更大,因?yàn)橥瑯拥捏w積v,它創(chuàng)造了更多價(jià)值。同理,如果dp[m+2v] - 2p > dp[m+v] - p,則說(shuō)明dp[m+2v]的價(jià)值更大。以此類推, 我們可以得到一個(gè)全新的序列:
[dp[m], dp[m+v] - p, dp[m+2v] - 2p, ... dp[i-v] - (i div v - 1)p]
這個(gè)時(shí)候,我們已經(jīng)消除了背包容量變化帶來(lái)的偏差,我們可以放心地從其中選擇最值作為最佳的狀態(tài)轉(zhuǎn)移了。但是還有一個(gè)小問(wèn)題,有可能最值是不成立的,舉個(gè)例子,如果說(shuō)我們發(fā)現(xiàn)dp[m+2v] - 2p的值是最大的,但是由于item這個(gè)物品最多獲取cnt個(gè),如果從m+2v這個(gè)狀態(tài)轉(zhuǎn)移到i,需要的數(shù)量超過(guò)cnt,那么這也是一個(gè)無(wú)效的轉(zhuǎn)移。我們需要拋開(kāi)它,繼續(xù)往下查找次優(yōu)的結(jié)果。
對(duì)于區(qū)間內(nèi)最值的維護(hù),單調(diào)隊(duì)列非常合適,我們可以保證隊(duì)首的元素是最優(yōu)的,如果隊(duì)首的元素不合法,那么我們可以很方便地彈出獲取次優(yōu)解。也就是說(shuō)我們通過(guò)單調(diào)隊(duì)列維護(hù)了[dp[m], dp[m+v] - p, dp[m+2v] - 2p, ... dp[i-v] - (i div v - 1)p]這個(gè)區(qū)間的最值,這也是單調(diào)隊(duì)列最常用的場(chǎng)景。
算法流程
我們整理一下上面的思路,可以整理出整個(gè)算法運(yùn)行的流程。
首先我們需要一重循環(huán)來(lái)遍歷物品,這個(gè)是肯定跑不了的。無(wú)論用什么算法用什么優(yōu)化,我們都需要遍歷物品,物品是決策的基礎(chǔ)。在01背包和二進(jìn)制表示法當(dāng)中,第二重循環(huán)就是直接遍歷背包容量了。但是顯然,在當(dāng)前算法當(dāng)中,我們不能這么做。因?yàn)榍拔漠?dāng)中說(shuō)到,我們需要用單調(diào)隊(duì)列來(lái)維護(hù)[dp[m], dp[m+v] - p, dp[m+2v] - 2p, ... dp[i-v] - (i div v - 1)p]這樣一個(gè)序列,所以我們需要按照這個(gè)序列的順序來(lái)遍歷背包容量。我們關(guān)注到起始狀態(tài)是dp[m],這個(gè)m代表分組,也就是物品體積的余數(shù)。
舉個(gè)例子,如果物品i的體積是5,那么m有0,1,2,3,4這5種取值,其實(shí)這也是5的余數(shù)。相當(dāng)于我們通過(guò)余數(shù)將背包容量進(jìn)行分組,這樣我們維護(hù)不同分組下的序列。這些分組拼裝在一起就是整個(gè)背包容量。
下面我們來(lái)看下代碼,結(jié)合上面的敘述會(huì)更直觀一些:
from collections import deque items = [(10, 3, 5), (5, 6, 3), (2, 2, 4)]if __name__ == '__main__':volume = 20dp = [0 for _ in range(volume+1)]# 單調(diào)隊(duì)列deq = deque()for item in items:cnt, v, p = itemfor i in range(v):# 每個(gè)i代表一組新的序列# 所以隊(duì)列需要清空重新開(kāi)始deq.clear()m = (volume - i) // vfor j in range(m+1):val = dp[j * v + i] - j * pwhile len(deq) > 0 and val >= deq[-1][0]:deq.pop()deq.append((val, j))if deq[0][1] < j - cnt:deq.popleft()dp[j * v + i] = deq[0][0] + j * pprint(dp[20])由于我們要實(shí)現(xiàn)單調(diào)隊(duì)列,并且從左右兩端都會(huì)進(jìn)行讀取操作,所以我們使用雙端隊(duì)列來(lái)實(shí)現(xiàn)。在之前的Python專題的文章當(dāng)中我們介紹過(guò)Python中deque的用法。除此之外,上面這段代碼當(dāng)中還有很多細(xì)節(jié),我們一點(diǎn)一點(diǎn)來(lái)看。
首先我們來(lái)看循環(huán)變量,最外層循環(huán)的是item,這個(gè)已經(jīng)確定了,i循環(huán)的是v的余數(shù)。即商品i的重量的余數(shù),也就是對(duì)于商品i來(lái)說(shuō)整個(gè)被背包容量分組的數(shù)量。然后我們針對(duì)每一個(gè)分組單獨(dú)計(jì)算最優(yōu)值。m表示這個(gè)數(shù)列的數(shù)量,就是背包容量減去余數(shù)之后除以商品的體積。所以我們要維護(hù)的序列就是[i, i+v, i+2v,..., i+mv],j循環(huán)的是這個(gè)序列。
為了公平起見(jiàn),我們要用dp[i]作為標(biāo)準(zhǔn)來(lái)衡量整個(gè)序列當(dāng)中的價(jià)值。對(duì)于狀態(tài)j * v + i來(lái)說(shuō),我們把它減去j個(gè)item的價(jià)值,也就是放到dp[i]一個(gè)起跑線來(lái)衡量?jī)r(jià)值。所以val = dp[j * v + i] - j * p。
針對(duì)單調(diào)隊(duì)列deq來(lái)說(shuō),這是一個(gè)遞減的隊(duì)列。也就是說(shuō)隊(duì)列頭部的元素是最大值,末尾是最小值。我們每次從尾端進(jìn)行插入,彈出所有比當(dāng)前小的元素,之后我們插入當(dāng)前的候選值。根據(jù)我們之前的推論,由于dp數(shù)組當(dāng)中的值在一輪遍歷之后會(huì)更新,所以我們需要把當(dāng)前的值和狀態(tài)一起存儲(chǔ)起來(lái),不能只存儲(chǔ)下標(biāo),否則當(dāng)更新過(guò)dp[j * v+ i]之后,就找不回來(lái)更新之前的值了。
這些邊界條件應(yīng)該還好,問(wèn)題不是很大,問(wèn)題比較大的是if deq[0][1] < j - cnt這個(gè)判斷。這個(gè)判斷當(dāng)中隱藏了兩個(gè)要點(diǎn),我們一個(gè)一個(gè)來(lái)說(shuō)。
首先,deq這個(gè)隊(duì)列當(dāng)中每個(gè)節(jié)點(diǎn)有兩個(gè)值,一個(gè)是val就是dp數(shù)組當(dāng)中存儲(chǔ)的價(jià)值,另一個(gè)是j,是序列的位置或者說(shuō)狀態(tài),注意,它不等于物品拿了的個(gè)數(shù)。由于題目當(dāng)中有物品拿取數(shù)量的限制,也就是說(shuō)并不是所有的轉(zhuǎn)移都是合法的。我們需要保證從隊(duì)列的狀態(tài)轉(zhuǎn)移到當(dāng)前狀態(tài)需要的物品個(gè)數(shù)小于等于最大數(shù)量cnt,如果大于就是非法。當(dāng)前狀態(tài)是j,隊(duì)列頭部元素狀態(tài)是deq[0][1],也就是說(shuō) j - deq[0][1] > cnt的話就非法。
因?yàn)閐eq隊(duì)列當(dāng)中頭部元素值是最大的,所以我們優(yōu)先考慮從頭部進(jìn)行轉(zhuǎn)移到當(dāng)前狀態(tài)。轉(zhuǎn)移是通過(guò)拿取物品進(jìn)行的,所以我們需要進(jìn)行物品數(shù)量的判斷,如果不滿足需要進(jìn)行彈出,這個(gè)是第一個(gè)要點(diǎn)。
第二個(gè)點(diǎn)是,為什么這里用if判斷而不是while呢?
因?yàn)閷?duì)于j來(lái)說(shuō),j-1的狀態(tài)已經(jīng)更新過(guò)了,也就是說(shuō)隊(duì)列頭部的元素必然對(duì)j-1是合法的。也就是說(shuō)j-1的數(shù)量距離j-1最多也就是cnt,那么對(duì)于j而言最多也就增加了1,我們最多只需要一次彈出就好了。當(dāng)然也可以用while循環(huán),只不過(guò)沒(méi)有必要。所以如果看不懂的話,這里寫(xiě)成while循環(huán)也是一樣的。
最后,我們來(lái)分析一下它的復(fù)雜度。對(duì)于物品i來(lái)說(shuō),它如果它的體積是v,那么我們一共可以分成v組。每組當(dāng)中的數(shù)量是volume / v,所以這兩者相乘就剛好是背包體積V的狀態(tài)數(shù)。你可能還會(huì)覺(jué)得我們使用了單調(diào)隊(duì)列會(huì)有開(kāi)銷,的確,但每一個(gè)元素最多進(jìn)出單調(diào)隊(duì)列一次,也就是說(shuō),對(duì)于每一組序列,單調(diào)隊(duì)列是
的復(fù)雜度,和遍歷的復(fù)雜度一致,所以使用單調(diào)隊(duì)列并不會(huì)整體增加復(fù)雜度的規(guī)模,只會(huì)增加常數(shù)。總結(jié)
到這里,多重背包的單調(diào)優(yōu)化解法就介紹完了。單調(diào)優(yōu)化是動(dòng)態(tài)規(guī)劃算法當(dāng)中非常常用的一種優(yōu)化方法,并不只是可以應(yīng)用在多重背包問(wèn)題上,在許多場(chǎng)景下都適用。不過(guò)前提是需要我們對(duì)于狀態(tài)之間的關(guān)系,以及轉(zhuǎn)移前后帶來(lái)的影響全部思考清楚。
從代碼上來(lái)說(shuō),動(dòng)態(tài)規(guī)劃實(shí)在是非常簡(jiǎn)單,一般不會(huì)超過(guò)幾十行,我們今天的算法主體也才12行,但是這短短的代碼中間藏了大量的信息和思考。對(duì)于初學(xué)者而言,第一次學(xué)習(xí)的時(shí)候會(huì)一臉懵逼是正常的,但是仔細(xì)冷靜下來(lái),少關(guān)注一些術(shù)語(yǔ),多思考一些本質(zhì)的原理,其實(shí)沒(méi)那么難,一遍看不懂多看幾遍,總會(huì)有那么一個(gè)時(shí)刻,讓你有靈光一閃的感覺(jué)。
今天的文章就是這些,如果覺(jué)得有所收獲,請(qǐng)順手點(diǎn)個(gè)關(guān)注或者轉(zhuǎn)發(fā)吧,你們的舉手之勞對(duì)我來(lái)說(shuō)很重要。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的多重背包单调队列优化思路_动态规划入门——多重背包与单调优化的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: mysql的临时表有哪几种
- 下一篇: 历史文件夹_Win10备份文件教程:备份