【dispatching】
(這題國內(nèi)數(shù)據(jù)極弱,暴力能80,下面的解法1,平衡樹即使用Splay Tree實(shí)現(xiàn)都能AC)
枚舉管理者所在的結(jié)點(diǎn),然后在這個結(jié)點(diǎn)及其所有后代中找到權(quán)值前K小的,使得它們的權(quán)值和不超過限制,要求求出K的最大值。
解法1:將每個結(jié)點(diǎn)及其所有后代的權(quán)值存在一個平衡樹里,然后,對于每個結(jié)點(diǎn),若不是葉結(jié)點(diǎn),就將其所有的子樹中,找到最大的那棵子樹,它所表示的平衡樹不動,將其它的子樹表示的平衡樹上的所有結(jié)點(diǎn)強(qiáng)行拆開,并插入到這個子樹表示的平衡樹中(這叫啟發(fā)式合并),別忘了將該結(jié)點(diǎn)本身也合并到這棵平衡樹里。合并好了以后,通過每個點(diǎn)記錄的sum(下面說明),用類似于求結(jié)點(diǎn)排名的方法,就可以求出K的最大值了。
容易證明,這樣合并的話,在整個過程中插入的次數(shù)是O(NlogN)的,加上平衡樹插入本身的O(logN),總的時(shí)間復(fù)雜度為O(Nlog
2N)。
具體實(shí)現(xiàn)起來有一些技巧:
(1)可以發(fā)現(xiàn),在合并過程中,各個點(diǎn)本身并沒有變,而只是它們之間的關(guān)系發(fā)生了變化(從不在同一棵樹上變成在同一棵樹上了),因此為了節(jié)省空間,可以預(yù)先將所有的結(jié)點(diǎn)建好,每個結(jié)點(diǎn)除了自身權(quán)值以外還記錄mul(初始為1)、sz(初始為1)、sz0(初始為1)、sum(初始等于權(quán)值)等信息,其中mul為重復(fù)次數(shù),sz為考慮mul情況下的樹的大小,sz0為不考慮mul的情況下的樹的大小(也就是實(shí)際結(jié)點(diǎn)個數(shù)),sum表示樹上所有結(jié)點(diǎn)的總權(quán)值(這個當(dāng)然要考慮mul了)。之所以維護(hù)sz0,是因?yàn)槊看慰梢酝ㄟ^找到sz0最大的子樹不動來加速。插入的操作是void ins(int r, int No),表示將結(jié)點(diǎn)No(是結(jié)點(diǎn)不是值)插入到以r為根的子樹里,注意這個結(jié)點(diǎn)No的mul可能大于1,因此在插入后維護(hù)的時(shí)候需要注意;
(2)由于在過程中會同時(shí)有多棵平衡樹存在,因此每個點(diǎn)還需要記錄一個額外的域rf,rf=-1表示該結(jié)點(diǎn)不是任何一棵平衡樹的根結(jié)點(diǎn),rf>=0表示該結(jié)點(diǎn)是原樹中編號為rf的結(jié)點(diǎn)表示的平衡樹的根結(jié)點(diǎn),同時(shí)記錄root[i]表示結(jié)點(diǎn)i表示的平衡樹的根結(jié)點(diǎn)編號,這樣就可以很方便地實(shí)現(xiàn)樹內(nèi)外的對接(注意這兩個值的維護(hù),尤其是將結(jié)點(diǎn)i本身合并到平衡樹里的時(shí)候,別忘了把root[i]改掉)。
解法2(正解):合并的時(shí)候使用可并堆(用左偏樹或斜堆實(shí)現(xiàn)),時(shí)間復(fù)雜度O(NlogN)。
【guard】
(這題其實(shí)很容易想到網(wǎng)絡(luò)流的話說……不過仔細(xì)研究一下就可以發(fā)現(xiàn),由于報(bào)告上來的只是某一個區(qū)間內(nèi)有木有人,而不是有幾個人,因此無法用網(wǎng)絡(luò)流的流量平衡來表示)
首先,某些區(qū)間報(bào)告木有人,這些區(qū)間內(nèi)的點(diǎn)肯定不符合要求了,可以將它們刪掉,然后對于剩下的(可能是若干段)區(qū)間,合并起來,對結(jié)點(diǎn)從左到右重新編號(這一步可以用線段樹,也可以排序后直接掃描,見
這里,本沙茶比賽時(shí)為了省事直接上線段樹了),然后就只需要考慮那些報(bào)告有人的區(qū)間了囧……當(dāng)然,這些區(qū)間的端點(diǎn)也是要對應(yīng)地重新編號的,編號完了以后,將所有包含別的區(qū)間的區(qū)間刪去(因?yàn)樗鼈兊拇嬖谀居幸饬x),方法:對于所有重編號后的區(qū)間,將其按照先左端點(diǎn)遞增,后右端點(diǎn)遞減排序,然后設(shè)置一個棧,保證棧中的區(qū)間左右端點(diǎn)均嚴(yán)格遞增,按照排序之后的順序掃描每個區(qū)間,每次一個新區(qū)間入棧時(shí),棧頂所有右端點(diǎn)大于等于這個新區(qū)間右端點(diǎn)的區(qū)間出棧,再將新區(qū)間入棧,這樣最后棧中的區(qū)間就是保留下來的區(qū)間了囧……而且已經(jīng)排序……
然后,如果問題是求至少要多少個人的話,就是一個經(jīng)典的貪心模型了,掃描排序后的區(qū)間,如果一個區(qū)間內(nèi)還木有任何人,就在它的右端點(diǎn)處放一個人。顯然,除了這些放了人的區(qū)間的右端點(diǎn)之外,其余的點(diǎn)肯定都不是必須放人的(因?yàn)楝F(xiàn)在已經(jīng)有一個方案使它們不放人了)。現(xiàn)在的問題是,這些右端點(diǎn)處是否必須放人。
設(shè)F[i]為第i個區(qū)間及其之后的區(qū)間當(dāng)中,若要每個區(qū)間內(nèi)都有人,至少要幾個人。若第i個區(qū)間的右端點(diǎn)不小于最后一個區(qū)間的左端點(diǎn),則F[i]=1(在第i個區(qū)間右端點(diǎn)放一個人即可),否則,找到最小的j,使得第i個區(qū)間的右端點(diǎn)小于第j個區(qū)間的左端點(diǎn)(這個顯然可以二分查找得到),則F[i]=F[j]+1。這樣從后到前推一遍即可。然后,再從前到后,按照上面的貪心算法的流程掃一遍,如果遇到第i個區(qū)間的右端點(diǎn)需要放人:若第i個區(qū)間的長度為1,顯然這里必須放人,否則嘗試在第i個區(qū)間的倒數(shù)第2個位置(即右端點(diǎn)之前的那個位置)放人,而右端點(diǎn)不放人(可以證明,不會后來又把這個右端點(diǎn)這里放上人的),類似用二分查找的方式找到這個j,然后看一下(_F + F[j] + 1)(_F為第i個區(qū)間前面放人個數(shù))是否大于K,若大于K,則第i個區(qū)間的右端點(diǎn)必須放人,否則不必須放人。
此外還有兩種特殊情況,一是M=0的時(shí)候在去包含的時(shí)候要特判一下(其實(shí)這時(shí)可以直接特判,若N=K,則所有的地方都必須放人,否則就是-1);二是,如果去掉那些肯定木有人的點(diǎn)后,剩下的點(diǎn)的個數(shù)剛好等于K(不可能小于K的,因?yàn)榭隙ㄓ薪猓瑒t不用貪心了,剩下所有的地方肯定都必須放人。
總時(shí)間復(fù)雜度O(NlogN + MlogM);
這里千萬要注意的一點(diǎn)是,應(yīng)該先將所有區(qū)間重編號再去包含,而不應(yīng)該先去包含再重編號!!!因?yàn)榭赡苡袃蓚€區(qū)間本來是不包含的,但重編號以后,包含了。(本沙茶比賽的時(shí)候就是這里木有注意到,跪了3個點(diǎn),杯具了)【kunai】
40分的暴力做法:枚舉兩個動點(diǎn),求出它們是否會相撞以及相撞的時(shí)間,然后將所有相撞事件按照時(shí)間排序,掃描排序后的每個事件,如果相撞的兩個動點(diǎn)都還在,就將它們消去,然后可以求出每個動點(diǎn)移動的距離,求一下并集(線段樹)即可;(本沙茶當(dāng)時(shí)主要是實(shí)在木有時(shí)間寫這題了,因此連暴力都木有寫)
AC做法:很明顯,一個動點(diǎn)一旦被消去,就不會再次出現(xiàn)了,因此對于每一個點(diǎn),只需要找到最先和這個點(diǎn)相撞的點(diǎn)即可。顯然能夠相撞的兩個點(diǎn)必然位于同一行/列/對角線上且方向滿足一定限制(其實(shí)一共只有6種情況:同一行一種,同一列一種,同一左上右下對角線兩種,同一右上左下對角線兩種)。因此,枚舉這6種情況,直接用掃描法得到最先與它相撞的動點(diǎn),取相撞時(shí)間最小的那個即可,如果都找不到,這個點(diǎn)就是永存的。這樣相撞事件的總數(shù)就減少到了O(N)級別,總時(shí)間復(fù)雜度就是O(NlogN)。
注意:有可能在同一時(shí)間有多個動點(diǎn)在同一位置相撞,因此,對于同時(shí)發(fā)生的相撞事件,應(yīng)該將它們一起處理,涉及到的點(diǎn)一起消去。