noip2018——题解总结
近期正在瘋狂復習某些東西,這篇博客盡量年底更完……(Day2T2除外)
?
好了,所有的希望都破滅了,原來這就是出題人的素質。——一個被欺騙的可憐 $OIer$
人生中倒數第三次 $noip$ (Maybe it is),好好珍惜這快樂的一段人生吧……
$OYYP$ 和 $WXJ$ 神仙都考得很好,然后快樂地回廣州二中繼續修仙了……其他大佬們也都穩穩地游冬令營了吧……我可能是要單獨留守北京的最菜選手了……
想看 $Day1$ 各種段子的可以空降。
游記
Day 0
啥都沒復習,慌得一批。
打了個自閉模擬賽,第一題輸入寫錯了,第二題推不出來,準備退役,yeah。
下午跟大佬們去聽聽 CXM 的考前叮囑,又給我們講了一遍 $NOILinux$ 的相關操作,尤其是終端編譯(我考試用了嗎?……)我順便記了一波。
然后去借(搶)$LHC$ 的電腦,用他裝的虛擬機練了一波終端。
虛擬機真卡,噴。
用了兩遍命令行發現沒啥問題,就放心了。
晚上早早回到家,瞎**復習了一波簡單題,又看了看數據結構模板。誒,越看越虛,我要是連相對簡單的 $Day1$ 都不會做我我就有望退役了。
吃完飯先裝 $NOILinux$。
裝了好長時間(二十分鐘?),發現虛擬機進不去導入的 $ubuntu$。
上網查錯誤信息無果。
我換家里的臺式電腦,又裝了半天,發現還是進不去?
為啥啊?……
我上 $NOI$ 官網下載 $iso$ 試試。
結果官網下載特別慢,顯示要用 $40$ 多分鐘。
不對啊,我之前從郵箱傳回來的 $iso$ 文件只要 $10$ 多分鐘啊?
我就大膽猜想是郵箱傳壞了。
于是等官網的 $iso$ 下完了,我再導入虛擬機,誒,能用。
然后我就用低端的虛擬機寫了道聯合權值,練了下對拍等操作,就睡覺去了。
Day 1
早早地起來去北師大附,路上還挺虛的,用手機復習了一遍逛公園那道題用拓撲排序方法怎么做來著。
到了考場大門,發現我的幾個同學也都剛好到。然后我就 $\%\%\%$ 他們咯。
俗話說“膜高一尺,分高一丈”嘛!向大佬膜拜,分能越膜越高。
進了考場,找到我的位置就坐下啦。
噗……這顯示屏的屏幕分辨率不對啊……顯示的東西都是扁扁的,配置真垃圾。
公布了電腦密碼之后我就找設置,想改屏幕分辨率,然后發現這是最高的了,而且只能調三種分辨率……另外兩種更可怕……
電腦配置差真的很無語。
$SYF$ 大佬坐在靠窗戶的位置,我望了一下她,繼續 $\%$ 了起來。
?
考前幾分鐘公布了試題密碼,只讓看題,不讓寫。
打開壓縮文件,我把東西全拖出到主文件夾。
先看了下規則文檔,今年開始改規則了,每道題的程序要放在不同的文件夾下,以前就給個考號文件夾,三道題的程序啥的一起放進去就可以了。
然后去看題。三道 $1s/512MB$ 的題,突然感覺很虛。
先看 $T1$,好像不太難,掃了一分鐘就看 $T2$ 了。
看完 $T2$ 又虛了,好像沒什么思路,跟去年的?$D1T1$ 小凱的疑惑?差不多的感覺……
三分鐘之后去看 $T3$,來回看樣例和數據,只能上來就想到二分答案,然后還不會判斷。
涼,看完今天三道題感覺好虛啊,我怎么什么都不會。
?
到點可以寫代碼了,我先敲了缺省源,存到每道題的目錄下。
然后做 $T1$。
然后我發現 $T1$ 暴力好像是 $O(n^2)$ 級別的??
也就是說我連 $T1$ 都不會做了?
涼,當時想退役了。
周圍已經響起啪啪的鍵盤聲了,然后我就慌了,趕緊拿筆推樣例。
然后就猜想,是不是可以做個倍增,快速求區間最小值,然后在分治的時候把區間所有數都減掉它。再以這個被減成 $0$ 的最小值為分界,繼續分治,減兩邊的區間?
但是怎么找到最小值的位置?要以那個位置為切割點,分治遞歸兩邊呀!
我當時想著 $ST$ 表好像只能求最小值,求不了位置……然后以為不可做……(后文的題解證實最小值的位置連個 $sb$ 都會找)
然后發現可以反過來做:把相鄰且相同的數的區間記為同一個區間,然后每次找數最大的區間,把它減成與它相鄰的區間中,數最大的那個。
這樣倒是可以,把分治轉成合并,就不用考慮那些 zz 問題了。
然后碼了一波,發現自己寫區間合并的代碼能力真差……
合并的時候,只要改被減區間的兩端點的歸屬就可以了。
無腦碼了挺久,大概寫到 $9:20$ 吧。小樣例很快測過了。
?
再次看到 $T3$,回想了一下老師講過的那些 樹上 $dp$ 的特征,然后注意到這題跟 樹上 $dp$ 的性質很像,對于一個點,賽道要么經過這個點和它的兩條兒子鏈,要么經過這個點、它的一條兒子鏈和它的祖先鏈(并且只能向祖先傳一條兒子鏈)。
那就是貪心了吧?把一個點的所有兒子傳來的鏈長排序,對于每一條兒子鏈,把它刪掉(防止另一條鏈取到它自己),貪心取另一條 與它相接的長度大于等于 二分的答案$mid$ 的一條兒子鏈,再把那條鏈刪掉,記賽道數 $+1$;如果沒有能與它連成合法賽道的另一條兒子鏈,就把它傳給祖先鏈。祖先鏈只能選一條最長的兒子鏈傳上去。
當然特判一下所有長度 $\ge mid$ 的兒子鏈,把它們自組成賽道就行了。
這不是 $dp$ 啊?貪心??
但是又推了一下,好像這么做就可以了,確實不用 $dp$。
于是我就嘗試了一波,$10$ 幾分鐘后寫完了。當然我比較懶,直接用了一種支持加數、二分查找(lower_bound)和刪數的 $STL$:$set$(其實不是這個,后面會提到)。
試了一下前兩個小樣例,都過了。此時 $10:10$ 左右,轉戰 $T2$。
?
一看到 $T2$ 又懵了一下,發現還是不會做。好像很結論啊
于是我手寫了一組小數據($2\space 5\space 7$),發現可以不要 $7$,因為它能被 $2,5$ 表示出來。
這樣答案 $m$ 就是 $2$ 了。答案肯定不會是 $1$,因為這個集合能表示的數的集合是 $2,4,5,6,7,8,9,……$。
那還有 $m=2$ 的方案嗎?經過嘗試發現沒有。
好像不能用?不在原集合的數?表示出等價的貨幣系統啊?
感覺這個結論很顯然,簡單想想大概是因為一個數能表示的集合只與自己等價,再深入證明我當時也不清楚了。
進一步推,也就是說只需要看一下原集合中一個數是否能被原集合中其它一些數表示出來
然后看了一下數據范圍,$T\times n\times a_i=5000w$,且 $a_i$ 是正整數。那就 排序+背包轉移?
**,感覺 $T2$ 比 $T1$ 還簡單了。
**,感覺剛開考時自己就是個傻逼。
$5$ 分鐘抓緊碼完代碼。
$10:30$ 的時候三道題就都寫完了。
?
然而三道題都還沒測大樣例,我就著手先測 $T2$ 大樣例了。
一測,沒過!什么情況啊?
很快發現背包轉移寫錯了,能把轉移的 $true$ 重新刷成 $false$。
迅速改了一下就對了。
?
然后去測 $T1$。
一測就死循環了,我瞬間懵了。
看了下優先隊列的判退出是怎么寫的。
然后發現我又 sb?了:$if(l[u]==1\space \&\&\space r[u]==6)\space break;$
$r[u]$ 應該判等于 $n$,我測第一組樣例的時候直接把樣例的 $n=6$ 寫上去了。
然而改完了還是死循環。
為什么??合并區間有問題嗎?按理說合并區間最多只要 $n-1$ 次啊,最后就只剩下一個區間 $[1,n]$,然后退出去。
我加了個調試特判,讓優先隊列跑 $10w$ 次就中途退出輸出累積得答案。
然后驚奇的發現答案對了?!
那為什么會死循環?
這個問題坑了我好久。調了無數遍,才發現原來我沒判區間位于左右邊界的情況,導致區間向下標為 $0$ 的位置(左邊界之外)合并,最終合并出的區間的左端點就小于 $1$,就出不去了。
其實好像預處理也寫的不太對,但這里不好描述是怎么改的。
大概 $11:15$ 的時候終于過了 $T1$ 的大樣例。由于代碼的調試輸出太多,弄得傷痕累累,我特意重新碼了一下格式。
?
最后測 $T3$ 的大樣例。
不出所料,又沒過。
但是我盯了半天代碼沒看出哪里有問題,好像寫的都沒錯啊?
于是我趕緊yy起了 $hack$ 數據。
然后第二組數據就 $hack$ 掉了:
4 1
1 2 1
2 3 2
2 4 2
答案明顯是 $4$,然后我的程序輸出 $3$。
我就疑惑了,下面兩條邊怎么沒有合并?
我輸出了一下在 $2$ 號點合并兒子鏈時,記錄鏈長的 $set$ 存了多少個數。
然后發現只有 $1$ 個數。
不是插入了兩個 $2$ 嗎?
想了半天,我突然意識到了:$set$ 是不可重集,相同的數只存一個。(這就是之前為什么說其實不是 $set$)
我趕緊改成 $multiset$,再測大樣例,還行,跟自己想的一樣過了。
再過幾分鐘就 $11:30$ 了吧。
?
也就是說我可以裝作今天 $AK$ 了?
不過我沒有立刻放松,反復地查文件名、輸入輸出、編譯是否還能過等各種細節,防止又翻車了。(我去年 $D1T2$ 打錯大小寫然后爆零了)
多虧最后 $10$ 分鐘發現并刪除了 $T3$ 一堆調試用的 $getchar$, 不然我直接交上去就可能 $T$ 了。(不知道為什么本機測就沒事)
呼,累+虛+緊張。等著收卷吧。
出了考場之后意識到一個問題:$T3$ 用 $STL$ 貌似會被卡常。我考試的時候沒用終端,也沒注意跑的速度。
眾所周知,在不開 $O2$ 的 $noip$ 老爺機上跑 $STL$ 有多慢。
算算時間,$5\times 10^4\times log(5*10^8)\times log(?) = 150w\times 玄學$??
樹上的總復雜度姑且認為是 $O(n\times 玄學)$ 級別的?
均攤復雜度是 $O(玄學)$ 級別的?
誒,心態崩了。測評的時候 極限數據要是真被卡常 我就虛上天了。
?
考后發現 $D1T1$ 居然是 $noip2013\space D2T1$ 原題??人名都不改??
而且我還沒復習那道題??
$noip$ 出原題真的沒事嗎,不違反規定嗎,
等等……中國信競規定都是他們出的,他們就算真做的有出入,誰敢管啊
總之 $I'm\space so\space moved$。
回到家之后我就驚奇地發現:只是這樣嗎?
Day1三道原題(或近似)
看看知乎
T2
T3
?
$Day1$ 就出成這樣?幾乎是三道原題,sdyg去吧。
三年OI一場空,三道原題見祖宗。NOIP=POI*N。
更可笑的是,有人在考前幾天發現某些網站上(如bzoj)的原題被關閉了,然后就猜出了是NOIP原題。
行了,準備禁他們的賽。
出題組疑似在 $2013$ 年泄露 $noip2018$ 的試題,嚴重違反公平競賽原則,也禁賽三年。
對了,驗題組也禁賽三年。
?
一覺睡到晚上。誒,起來接著復習。
……看點啥?
再考原題怎么辦,所以先看看 $noip2012$ 的同余方程。
剩下的可能就是復習板子題了?
看了看去年的狀壓、$splay$、替罪羊樹等知識點,然后……了一些事情,就接著睡了。
Day 2
今天到了考點依然先惡膜大佬們。不過今天是不是反向膜拜了,感覺 $rp$ 都膜沒了。
誒,好虛啊。
公布題目密碼后就按順序看題。
$T1$ 題目我一上來就看錯了,以為題目的意思是邊可以任意重復走。
$T2$……什么東西……為什么 $n,m$ 差這么多……
$T3$……更沒想法了,似乎不是數據結構題。
我突然意識到前一天晚上的復習可能沒用了。
這就是考前復習的內容必不考的定理?
?
很快就到點開考了。我畫了畫圖,題目給的兩組樣例都對了,于是我按照我看錯的題意碼了 優先隊列 $bfs$。
一測,前兩組樣例沒啥問題。
我想,$T1$ 應該不用太花心思調了吧?
然后大樣例直接 $WA$ 上天了。
我想:不可能啊,這做法都有問題嗎?
我查了一下我怎么跑錯的大樣例,一段時間后發現自己讀錯題了:每條邊經過一次后,就不能再以第一次的方向走這條邊了。
這導致我很慌 OwO
我又手算了一波,發現如果是樹的話依然很好做,只要優先跑編號小的兒子,然后把以這個兒子為根的子樹跑完再出來就可以了。
那為什么會有 $n=m$ 的數據?
也就是說要刪一條邊?
當時我并沒有想到可以枚舉刪哪條邊,因為我沒想到可以預處理把每個點的所有兒子排序,以為枚舉套樹上排序是 $O(n^2\times log(n))$ 的,實際上分開做就是 $O(n^2)$ 的了……(這個故事證明了我在暴力方面思想僵化)
就因為這一點我苦算了好久 $T1$……心態爆炸
最后我yy出了一個預處理刪掉哪條邊的方法,具體就是找到那個唯一的環,然后模擬跑一遍環,看準備沿環邊走到哪個兒子點時,退回去走環的另一側?比 繼續走下去?更優即可。把這條邊刪掉就行了(別忘了是無向圖,要刪兩條單向邊)。(考后發現這個做法有問題,具體見題解部分)
最后調過了各種奇怪的問題,終于過大樣例了。
此時已經 $9:50$ 了吧……好氣啊……
看了下 $T2$,按理說這個位置該放狀壓 $dp$ 了。
好像 $n=8$ 很可以入手的樣子。
但我算了一波就發現根本就沒法入手……
然后我可能見了鬼了,無腦推了一波式子,猜想答案跟 $2^n$ 的很多倍有關……
但很快我意識到上面的數要比下面的對應位置的數小,所以我改了推法。
推了一小時,我成功推翻了我yy的式子。原因是如果 上面的路徑在前面某一位的數 比 下面的路徑在對應位的數 小,那上面的路徑的后面所有數貌似是可以隨便填的。
浪費時間的我最終自閉了。
我悶聲碼了 $20$ 分暴力,看 $T3$ 去了。
此時已經過 $11:00$ 了。
?
$T3$ 還想正解嗎?不存在的。
想了想感覺不可做,于是就寫了寫 $2000$ 規模的暴力樹上 $dp$ ,復雜度 $O(nm)$ 吧。
至于 $-1$ 的情況,只要判斷 國王是否要求一條邊連接的兩個點都不駐扎軍隊 就可以了,只有這種情況輸出 $-1$。
?
都碼完了,再檢查一下文件名什么的,作為暴力選手的我就遺憾滾粗了。
$Day2$ 的期望得分…… $100+20+44=164$ ?
我好菜啊,$T2$ 沒有強行打表,$T3$ 也沒杠什么特殊情況。
其他選手至少都有思路吧,至少也是寫掛了,總比我這個一點沒寫的蒟蒻強。
唉,果然還是我太菜。$noip2018$ 就這么遺憾地考完了。
?
回去看鐘子謙發知乎消息說 $T2$ 是找規律?前 $65$ 分找規律可白送?不是狀壓 $dp$?
我**,我 $T2$ 好像 $20$ 分滾粗了……
$T3$ 是動態 $dp$?每個點維護一個轉移矩陣?
11.19 update:
$D2T2$ 我打錯了暴力??我把判邊界的 $m$ 打成了 $n$?而且數據太水導致現場沒發現?我去年的 $D2T3$ 發生過類似的暴力打掛事件??完了 $2\space 3$ 這組數據就把我卡了,看來我要因暴力退役了。
$D2T3$ 樹高 $\le 100$ 的 $8$ 分額外數據是白送的?……暴力修改兩點簡單路徑上的 $dp$ 值就可以了??行吧***(自動和諧)這特殊點我在考場上直接沒看見。其實即使看到了也不一定有時間調出來了吧……
完了完了……我還是這么不會打 $OI$……看來我只能拿夠大眾分退役辣。
看他們都聊得好開心(充滿了 $AK$ 的氣氛)
?
(我太菜了,所以這里沒有我的發言)
?
暈,這是可做的找規律題嗎……動態 $dp$ 什么時候成 $noip$ 考點了……
我不會寫啊……瑟瑟發抖……
看來今年又是一次教訓了,等出了成績之后我發一下吧(爆零預定)。
?
11.20 update:
$luogu$ 民間數據:$100+100+95+84+15+44=438$
$CCF$ 官方數據:$100+100+100+92+15+44=451$
我太菜了……大佬保佑我卡上 $PKUWC$ 的線吧……(12.?? update:我卡個毛,今年北大和清華的分數線居然一樣,都是 $480$)
?
11.28 update:
前幾天終于知道為什么這次的 $Day2T2$ 是這么一道垃圾題了……
原 $T2$ 出題人被張哥哥騙到一個小地方去講課了(他以為是 $ccf$ 辦的課),但根據 $ccf$ 規定,$NOI$ 系列比賽的出題人在期間是不能出去代課的,結果官方因為怕泄題,把原 $Day2T2$ 給刪了,然后草草地翻書翻了這么一道垃圾數學題,就趕緊補上去了。
$ccf$ 達成新成就——$2018$ 全年比賽全部出鍋($CTSC$ 收程序時關機導致丟了 $85$ 個人的程序 及 $Day2T2$ 正解被 $hack$,$NOI$ $Day1T2$ 卡特蘭數數據出錯,等等)。
?
題解
1.積木大賽
題目描述
春春是一名道路工程師,負責鋪設一條長度為 $n$ 的道路。
鋪設道路的主要工作是填平下陷的地表。整段道路可以看作是 $n$ 塊首尾相連的區域,一開始,第 $i$ 塊區域下陷的深度為 $d_i$ 。 春春每天可以選擇一段連續區間 $[L,R]$,填充這段區間中的每塊區域,讓其下陷深度減少 $1$。在選擇區間時,需要保證,區間內的每塊區域在填充前下陷深度均不為 $0$ 。春春希望你能幫他設計一種方案,可以在最短的時間內將整段道路的下陷深度都變為 $0$。
輸入輸出格式
輸入格式
輸入文件包含兩行,第一行包含一個整數 $n$,表示道路的長度。第二行包含 $n$ 個整數,相鄰兩數間用一個空格隔開,第 $i$ 個整數為 $d_i$。
輸出格式
輸出文件僅包含一個整數,即最少需要多少天才能完成任務。
輸入輸出樣例
輸入樣例#1
6
4 3 2 5 3 5
輸出樣例#1
9
說明
一種可行的最佳方案是,依次選擇: [1,6]、[1,6]、[1,2]、[1,1、[4,6]、[4,4]、[4,4]、[6,6]、[6,6]。
對于 $30\%$的數據,$1≤n≤10$;
對于 $70\%$ 的數據,$1≤n≤1000$;
對于 $100\%$ 的數據,$1≤n≤100000,\space 0≤d_i≤10000$。
?
noip2013 積木大賽原題。
但是我在考場上并沒看出來,于是yy了很愚蠢的算法(游記口胡過了)。
?
方法1(我的考場做法):
就是我們手玩這道題的話,如果是按照往下減的思路做的話,肯定會把最大的數先往下減,這樣就能把更長的一段數減到一樣小,使以后同時減的區間更大。
什么意思?就是這段數既然都一樣了,它們就可以縮成一個數。因為它們以后一直都可以同時減,最終減到 $0$。
比如 $5\space 4\space 4\space 4\space 3$,先把最大的 $5$ 減到 $4$ 后,我們就可以把前 $4$ 個 $4$ 同時減到 $3$,再把 $5$ 個 $3$ 同時減到 $0$。容易發現任何一段連續且相同的數以后都可以同時減。
然后就想出比較貪心的做法了?
具體做法呢,為了方便調試(也就是正好合并 $n-1$ 次區間),我開始時直接建立 $n$ 個區間,第 $i$ 個區間的左右端點都設為 $i$(表示初始時每個區間各自只有 $1$ 個數),也就是可以不用在預處理時 把連續的幾個相等的數合為一個區間。
然后用一個優先隊列存儲當前值最大的區間,每次把它減為 與它相鄰的兩個區間中值較大的那個區間的數,這樣可以保證合并的有序性(從大到小)。在每個區間的左右端點上記錄一下它們的屬于哪個區間,這樣要找一個區間的左右兩個區間的話,直接取。別忘了特判左端點是 $1$ 和右端點是 $n$ 的情況(考場上的悲慘調試)。
最終判一下
時間復雜度:由于初始時最多有 $n$ 個區間,最終合并成一個 左端點為 $1$,右端點為 $n$,的區間,且每次合并兩個區間必定使區間數 $-1$,所以最多合并 $n-1$ 次,套優先隊列是 $n\times log(n)$。
?
方法2:
就是你只想用分治……這樣重點就是找到區間內最小值的位置。
其實這完全可以做到啊 $QoQ$,至少線段樹就可以了(這就是為什么我在之前的游記的這題部分說道 區間最小值的位置連個 $sb$ 都會求),大不了 $D1T1$ 寫個線段樹,雖然難度明顯不對應,但線段樹也挺好寫的,人家也沒規定你簡單題就不能寫數據結構了(像 $OYYP$ 神仙就這么做了)。
當然 $SYF$ 直接yy了 $ST$ 表做法,就是你的 $ST$ 表直接記錄區間內最小值的位置,如果有多個最小值,記最左邊那個。
倍增的時候直接取兩邊記錄的位置的值,比較大小,然后把值小的位置 賦給倍增合并后的區間。
這樣就可以分治了……也沒繞什么彎,死磕一下也能磕出來。
?
方法3:
沒錯就是積木大賽那題的做法。他們說是差分?
其實不用說的那么高端吧,這種題就是個思維遞推,因為后面的數無法影響 前面的數減到 $0$ 的次數。
所以我們只考慮 一個數與它之前所有的數 都減到 $0$ 所需的總次數。對于一個數,
如果它比前一個小,那它只需要一直跟前面相鄰的數一起減,就能減到 $0$,所以不用加次數;
如果它比前一個大,那它需要比前面相鄰的數多減 $d_i-d_{i-1}$ 次。
?
這是我的考場代碼(方法1)
1 #include<cmath> 2 #include<cctype> 3 #include<cstdio> 4 #include<cstdlib> 5 #include<cstring> 6 #include<iostream> 7 #include<algorithm> 8 #include<queue> 9 #define mp make_pair 10 using namespace std; 11 inline int read(){ 12 int x=0; bool f=1; char c=getchar(); 13 for(;!isdigit(c);c=getchar()) if(c=='-') f=0; 14 for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0'); 15 if(f) return x; 16 return 0-x; 17 } 18 int n,a[100010],cnt,part[100010],v[100010],l[100010],r[100010]; 19 //int st[100010][20]; 20 int gs[100010]; 21 priority_queue<pair<int,int> >Q; 22 bool inq[100010]; 23 //int hd=1,tl; 24 long long ans; 25 int main(){ 26 //freopen("road.in","r",stdin); 27 //freopen("road.out","w",stdout); 28 n=read(); 29 int i,j; 30 for(i=1;i<=n;++i){ 31 a[i]=read(); 32 part[++cnt]=cnt, v[cnt]=a[i], l[cnt]=r[cnt]=i, Q.push(mp(a[i],cnt)), inq[cnt]=1; 33 gs[i]=cnt; 34 /* 35 st[i][0]=a[i]; 36 for(j=1;j<=Log[i];++j) 37 st[i][j]=min(st[i][j-1],st[i-(1<<(j-1))][j-1]);*/ 38 } 39 int u;//,P=0; 40 41 while(!Q.empty()){ 42 u=Q.top().second,Q.pop(); 43 inq[u]=0; 44 45 ans+=(long long)v[u]; 46 if(l[u]==1 && r[u]==n) break; 47 if(l[u]!=1 && (r[u]==n || v[gs[l[u]-1]]>v[gs[r[u]+1]])){ 48 r[gs[l[u]-1]]=r[part[u]]; 49 gs[r[u]]=part[l[u]-1]; 50 //if(part[l[u]-1]==0){printf("aki:%d->%d %d %d->%d\n",u,part[u],l[u],v[u],v[part[u]]); break;} 51 part[u]=gs[l[u]-1]; 52 ans-=(long long)v[part[u]]; 53 //if(P%100==0) printf("%d\n",v[part[u]]); 54 //printf("merge:%d %d %d\n",part[u],l[part[u]],r[part[u]]); 55 if(!inq[part[u]]) Q.push(mp(v[part[u]],part[u])); 56 } 57 else{ 58 l[gs[r[u]+1]]=l[part[u]]; 59 gs[l[u]]=part[r[u]+1]; 60 //printf("%d %d\n",r[u],gs[r[u]+1]); 61 //if(part[r[u]]==0){printf("%d->%d %d %d->%d\n",u,part[u],r[u],v[u],v[part[u]]); break;} 62 part[u]=gs[r[u]+1]; 63 ans-=(long long)v[part[u]]; 64 //if(P%100==0) printf("%d\n",v[part[u]]); 65 //printf("merge:%d %d %d\n",part[u],l[part[u]],r[part[u]]); 66 if(!inq[part[u]]) Q.push(mp(v[part[u]],part[u])); 67 } 68 } 69 printf("%lld\n",ans); 70 return 0; 71 } View Code?
2.貨幣系統
題目描述
在網友的國度中共有 $n$ 種不同面額的貨幣,第 $i$ 種貨幣的面額為 $a[i]$,你可以假設每一種貨幣都有無窮多張。為了方便,我們把貨幣種數為 $n$、面額數組為 $a[1..n]$ 的貨幣系統記作 $(n,a)$。
在一個完善的貨幣系統中,每一個非負整數的金額 $x$ 都應該可以被表示出,即對每一個非負整數 $x$,都存在 $n$ 個非負整數 $t[i]$ 滿足 $a[i] \times t[i]$ 的和為 $x$。然而, 在網友的國度中,貨幣系統可能是不完善的,即可能存在金額 $x$ 不能被該貨幣系統表示出。例如在貨幣系統 $n=3$, a=[2,5,9]$ 中,金額 $1,3$ 就無法被表示出來。
兩個貨幣系統 $(n,a)$ 和 $(m,b)$ 是等價的,當且僅當對于任意非負整數 $x$,它要么均可以被兩個貨幣系統表出,要么不能被其中任何一個表出。
現在網友們打算簡化一下貨幣系統。他們希望找到一個貨幣系統 $(m,b)$,滿足 $(m,b)$ 與原來的貨幣系統 $(n,a)$ 等價,且 $m$ 盡可能的小。他們希望你來協助完成這個艱巨的任務:找到最小的 $m$。
輸入輸出格式
輸入格式
輸入文件的第一行包含一個整數 $T$,表示數據的組數。
接下來按照如下格式分別給出 $T$ 組數據。 每組數據的第一行包含一個正整數 $n$。接下來一行包含 $n$ 個由空格隔開的正整數 $a[i]$。
輸出格式:
輸出文件共有 $T$ 行,對于每組數據,輸出一行一個正整數,表示所有與 $(n,a)$ 等價的貨幣系統 $(m,b)$ 中,最小的 $m$。
輸入輸出樣例
輸入樣例#1
2
4
3 19 10 6
5
11 29 13 19 17
輸出樣例#1
2
5
說明
在第一組數據中,貨幣系統 $(2,[3,10])$ 和給出的貨幣系統 $(n,a)$ 等價,并可以驗證不存在 $m<2$ 的等價的貨幣系統,因此答案為 $2$。 在第二組數據中,可以驗證不存在 $m<n$ 的等價的貨幣系統,因此答案為 $5$。
數據范圍與約定
對于 $100\%$的數據,滿足 $T≤20,n,a[i]≥1$。
?
首先可以把原集合的數從小到大排序,這樣就能快速找到并刪除 原集合中 能被集合中的一些數表示出來的數,它們一定是無用的。
那為什么這樣做完后就得到了最終答案集合呢?
這里介紹一個容易理解的證明方法:
如果想找到一個更小的 $m$,那至少需要在集合中刪掉 $k$ 個數($k\gt 1$),再加上最多 $k-1$ 個數,等價代替刪掉的那些數的表示情況。保證刪除的數不會再被加入,因為這樣的一對操作沒對集合做出改變。
但這樣做一定搞不出與原集合等價的更優新集合。原因是:
假設刪除的 $k$ 個數的最小值為 $x$,你加入的數至少有一個是 $x$ 的因子(且這個因子不等于自己,上面已經保證過)。
而原集合中存在 $x$,說明原集合中沒有其它一個或一些數能表示出 $x$,也就必定沒有 $x$ 的因子(否則那個 $x$ 的因子直接表示出 $x$ 了)。
但你加入了一個 $x$ 的因子,這樣就一定不與原集合等價,因為你至少新表示了加入的 $x$ 的因子本身,而原集合不能表示這個數。
綜上,已經搞不出與原集合等價的更優新集合。
?
看一下數據范圍就知道是 排序+背包轉移了……
不過數據比較水,寫個爆搜判斷一個數是否能被表示出來,瞎搞些優秀的剪枝,都可以過(比如 $SYF$)。
1 #include<cmath> 2 #include<cctype> 3 #include<cstdio> 4 #include<cstdlib> 5 #include<cstring> 6 #include<iostream> 7 #include<algorithm> 8 using namespace std; 9 inline int read(){ 10 int x=0; bool f=1; char c=getchar(); 11 for(;!isdigit(c);c=getchar()) if(c=='-') f=0; 12 for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0'); 13 if(f) return x; 14 return 0-x; 15 } 16 int T,n,a[110]; 17 bool vis[25100]; 18 int main(){ 19 //freopen("money.in","r",stdin); 20 //freopen("money.out","w",stdout); 21 T=read(); 22 while(T--){ 23 int ans; 24 n=ans=read(); 25 int i,j; 26 for(i=1;i<=n;++i) a[i]=read(); 27 sort(a+1,a+n+1); 28 memset(vis,0,sizeof vis); 29 for(i=1;i<=n;++i){ 30 if(vis[a[i]]){--ans; continue;} 31 vis[a[i]]=1; 32 for(j=a[i]+1;j<=25000;++j) vis[j]|=vis[j-a[i]]; 33 } 34 printf("%d\n",ans); 35 } 36 return 0; 37 } View Code?
3.賽道修建
題目描述
C 城將要舉辦一系列的賽車比賽。在比賽前,需要在城內修建 $m$ 條賽道。
C 城一共有 $n$ 個路口,這些路口編號為 $1,2,…,n$,有 $n?1$ 條適合于修建賽道的雙向通行的道路,每條道路連接著兩個路口。其中,第 $i$ 條道路連接的兩個路口編號為 $a_i$ 和 $b_i$ ,該道路的長度為 $l_i$。借助這 $n?1$ 條道路,從任何一個路口出發都能到達其他所有的路口。
一條賽道是一組互不相同的道路 $e_1,e_2,…,e_k$,滿足可以從某個路口出發,依次經過道路 $e_1,e_2,…,e_k$(每條道路經過一次,不允許調頭)到達另一個路口。一條賽道的長度等于經過的各道路的長度之和。為保證安全,要求每條道路至多被一條賽道經過。
目前賽道修建的方案尚未確定。你的任務是設計一種賽道修建的方案,使得修建的 $m$ 條賽道中長度最小的賽道長度最大(即 $m$ 條賽道中最短賽道的長度盡可能大)。
輸入輸出格式
輸入格式
輸入文件第一行包含兩個由空格分隔的正整數 $n,m$,分別表示路口數及需要修建的 賽道數。
接下來 $n?1$ 行,第 $i$ 行包含三個正整數 $a_i,b_i,l_i$,表示第 $i$ 條適合于修建賽道的道路連接的兩個路口編號及道路長度。保證任意兩個路口均可通過這 $n-1$ 條道路相互到達。每行中相鄰兩數之間均由一個空格分隔。
輸出格式
輸出共一行,包含一個整數,表示長度最小的賽道長度的最大值。
輸入輸出樣例
輸入樣例#1
7 1
1 2 10
1 3 5
2 4 9
2 5 8
3 6 6
3 7 7
輸出樣例#1
31
輸入樣例#2
9 3
1 2 6
2 3 3
3 4 5
4 5 10
6 2 4
7 2 9
8 4 7
9 4 4
輸出樣例#2
15
說明
【輸入輸出樣例 1 說明】
所有路口及適合于修建賽道的道路如下圖所示:
?
需要修建 $3$ 條賽道。可以修建如下 $3$ 條賽道:
1. 經過第 $1,6$ 條道路的賽道(從路口 $1$ 到路口 $7$),長度為 $6+9=15$;
2. 經過第 $5,2,3,8$ 條道路的賽道(從路口 $6$ 到路口 $9$),長度為 $4+3+5+4=16$;
3. 經過第 $7,4$ 條道路的賽道(從路口 $8$ 到路口 $5$),長度為 $7+10=17$。 長度最小的賽道長度為 $15$,為所有方案中的最大值。
【數據規模與約定】
所有測試數據的范圍和特點如下表所示 :
其中,“分支不超過 $3$”的含義為:每個路口至多有 $3$ 條道路與其相連。 對于所有的數據, $2 ≤ n ≤ 50,000,\space 1 ≤ m ≤ n-1,\space 1 ≤ a_i,b_i ≤ n,\space 1 ≤ l_i ≤ 10,000$。
?
看到“長度最小的賽道長度最大”就知道外層要二分答案了……
那怎么判斷呢?運用一些樹形 $dp$ 的思想就可以了。
設當前二分的答案為 $x$。
考慮樹上的一個點,我們已經從下往上處理完了 以這個點為根的子樹中 能修建的賽道數量,每個兒子上傳了一個未完成的賽道,它們要么自己就是一條合法的賽道(直接把它們解決了),要么選出兩條相連成一條合法的賽道(也稱配對,即兩條賽道長度和 $\ge x$),要么上傳一條最大的無法完成的賽道(即長度 $\lt x$ 的未完成賽道,上傳時加上父邊長)。只能上傳一條未完成賽道是因為父邊只有一條,也只能讓一條賽道經過,那我們當然貪心選盡量長的,剩下的沒法兩兩配對的未完成賽道就不用了。
所以內層貪心做法比較顯然:把一個點的所有兒子上傳的未完成的賽道長 從小到大排序,然后每次取出并刪除最短的一條,二分找出并刪除 能與它拼成長度和 $\ge x$ 的一條賽道,總賽道數 $+1$;如果不存在這樣的賽道,就更新要上傳的賽道 為這條賽道——因為是按長度從小到大取,所以后取的未完成的賽道的長度一定更大,也就更應該上傳。
這樣貪心的正確性也是顯然的。證明:假如現在你任意舍棄若干對 能連成合法賽道的未完成賽道 中最長的一個上去,最好情況下也就是在這棵子樹中少弄出一條賽道,回到以某個祖先為根的子樹時多弄出一條賽道(因為上傳的未完成賽道的配對是一次性的),而不可能弄出更多。
在內層,只要湊夠 $m$ 條合法賽道就直接退出,回到外層二分。如果不迅速退出的話,樹的剩下部分都是白跑。這甚至不算“優化”,因為不這么做的話無論如何都穩穩的 $TLE$。
?
補充:注意一定要按長度從小到大取,而不能從大到小取,因為我們要盡量把一對短的未完成賽道 相連成合法賽道,而留長的未完成賽道 上傳上去,因此大的賽道可能并不需要配對。
舉個例子(每條邊上的數表示對應兒子上傳的未完成賽道的長度):
?
當 $x=15$ 時,第 $1,4$ 和 $2,3$ 個兒子明顯可以配對成 $2$ 條賽道,然后上傳那一條最長的長度為 $12$ 的未完成賽道。
總之在最優情況下,長的未完成賽道是配對還是上傳 只受短賽道的需求情況的影響,如果短賽道不需要它,這條長賽道完全可以上傳。因此從長到短取的話 不能確定取出的未完成賽道是配對更優還是上傳更優,而從短到長取就能確定(它還存在就說明短賽道不需要它,嘗試配對唄)。
?
所以這道題的重點:快速查數?刪數?線段樹?這題就要寫數據結構了嗎我的叔叔?
其實這些操作 $STL:multiset$ 都有……這種做法的時間復雜度在前面的游記中已經說過,是 $O(n\times log(n)\times log(5e8))$ 的,但 $multiset$ 的常數比較大。
1 // luogu-judger-enable-o2 2 #include<cmath> 3 #include<cctype> 4 #include<cstdio> 5 #include<cstdlib> 6 #include<cstring> 7 #include<iostream> 8 #include<algorithm> 9 #include<set> 10 #define N 50010 11 using namespace std; 12 inline int read(){ 13 int x=0; bool f=1; char c=getchar(); 14 for(;!isdigit(c);c=getchar()) if(c=='-') f=0; 15 for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0'); 16 if(f) return x; 17 return 0-x; 18 } 19 20 int n,m; 21 struct edge{int v,w,nxt;}e[N<<1]; 22 int head[N],cnt; 23 inline void add(int u,int v,int w){e[++cnt]=(edge){v,w,head[u]}, head[u]=cnt;} 24 int lim,num; 25 int dp[N]; 26 multiset<int>s; 27 multiset<int>::iterator it,tmp; 28 bool flag; 29 void dfs(int u,int fa){ 30 dp[u]=0; 31 for(int i=head[u];i;i=e[i].nxt) 32 if(e[i].v^fa){ 33 dfs(e[i].v,u); 34 if(flag) return; 35 } 36 s.clear(); 37 //printf("dfs:%d %d\n",u,lim); 38 for(int i=head[u];i;i=e[i].nxt){ 39 if(e[i].v^fa){ 40 //printf("it son:%d %d %d\n",e[i].v,dp[e[i].v],e[i].w); 41 if(dp[e[i].v]+e[i].w>=lim) ++num; 42 else s.insert(dp[e[i].v]+e[i].w); 43 //printf("%d\n",s.size()); 44 if(num>=m){flag=1; return;} 45 } 46 } 47 int cur,pd; 48 //printf("%d\n",s.size()); 49 for(it=s.begin();it!=s.end();it=s.begin()){ 50 cur=*it; 51 s.erase(it); 52 if(!s.size()){if(cur>dp[u]) dp[u]=cur; break;} 53 tmp=s.lower_bound(lim-cur); 54 //printf("%d %d %d %d %d\n",s.size(),lim,cur,*tmp,tmp==s.begin()); 55 if(tmp==s.end()){ 56 if(cur>dp[u]) dp[u]=cur; 57 } 58 else{ 59 s.erase(tmp); 60 ++num; 61 if(num>=m){flag=1; return;} 62 } 63 } 64 //printf("ret:%d %d %d\n",u,dp[u],num); 65 } 66 bool check(int mid){ 67 lim=mid, num=0, flag=0; 68 dfs(1,0); 69 return flag; 70 } 71 int main(){ 72 //freopen("track.in","r",stdin); 73 //freopen("track.out","w",stdout); 74 n=read(),m=read(); 75 int i,u,v,w; 76 int l=0,r=0,mid,ans; 77 for(i=1;i^n;++i) u=read(), v=read(), w=read(), r+=w, add(u,v,w), add(v,u,w); 78 while(l<=r){ 79 mid=(l+r)>>1; 80 //printf("binary:%d\n",mid); 81 if(check(mid)) ans=mid, l=mid+1; 82 else r=mid-1; 83 } 84 printf("%d\n",ans); 85 return 0; 86 } 87 /* 88 4 1 89 1 2 2 90 2 3 2 91 2 4 1 92 */ View Code實測 $multiset$ 就可以通過官方數據了,可能是官方數據比較水吧。
這里介紹三個優化(我考場上都沒用):
1. 提前判斷“未完成賽道長度 $\ge x$ 的情況”
之前說過,兒子上傳的未完成賽道 可能本身就是一條合法的賽道。所以可以在兒子處做上傳時就判斷它們。這樣可能提前湊夠賽道數,從而提早退出,再少跑樹的一些部分。
2. 搞掉常數,變成嚴格 $O(n\times log^2)$
如果仔細讀題,就會發現那些保證全部 $a_i=1$ 的點是菊花圖,實踐證明菊花圖狀的極端數據可以卡 $multiset$ 做法的常數(這就是為什么我luogu數據被卡了 $5$ 分)。所以我們考慮消掉常數。
當然這就不用 $multiset$ 了。
注意到一個長度序列從小到大排序后依次取,對于一個取出的長度 $x$,如果最小的能與它連成合法賽道的長度為 $y$,那對于下一個取到的長度 $x'$,因為 $x'\ge x$,所以 $y'\le y$。
然后你發現了什么?單調性!
所以把長度序列排序,在兩端各設一個指針,然后兩個指針相向掃就可以了,要確保左指針掃到的每個長度 都要通過右指針往左掃 盡量找到一個能與它配對的長度。
上傳的未完成賽道的長度是 右指針第一個跳過的長度。
- 當然 $SYF$ 還有一個復雜度、常數相同的做法。
3. 改小二分范圍
其實不用優化 $2$,只用這種優化也可以讓 $multiset$ 卡過菊花圖的極端數據!
你會發現菊花圖的答案很小,原因是最大答案不會超過樹的直徑。
而樹的直徑可以 $O(n)$ 求出(從根開始求兩次最遠點),所以把二分答案的初始上界改為樹的直徑即可卡過像菊花圖這樣的數據。
像我這種不愛思考的選手,考場上直接把二分初始上界設為所有邊權的和,出了考場后瑟瑟發抖怕被吊著卡。
以后還是得注意呀!畢竟這只是 $noip$。
?
人生中第一次 $AK$ 了一天正式比賽,留個紀念沒啥問題吧~(雖然這可能是全世界都 $AK$ 系列)
4. 旅行
題目描述
小 Y 是一個愛好旅行的 OIer。她來到 X 國,打算將各個城市都玩一遍。
小Y了解到, X國的 $n$ 個城市之間有 $m$ 條雙向道路。每條雙向道路連接兩個城市。不存在兩條連接同一對城市的道路,也不存在一條連接一個城市和它本身的道路。并且, 從任意一個城市出發,通過這些道路都可以到達任意一個其他城市。小 Y 只能通過這些道路從一個城市前往另一個城市。
小 Y 的旅行方案是這樣的:任意選定一個城市作為起點,然后從起點開始,每次可 以選擇一條與當前城市相連的道路,走向一個沒有去過的城市,或者沿著第一次訪問該 城市時經過的道路后退到上一個城市。當小 Y 回到起點時,她可以選擇結束這次旅行或 繼續旅行。需要注意的是,小 Y 要求在旅行方案中,每個城市都被訪問到。
為了讓自己的旅行更有意義,小 Y 決定在每到達一個新的城市(包括起點)時,將 它的編號記錄下來。她知道這樣會形成一個長度為 $n$ 的序列。她希望這個序列的字典序最小,你能幫幫她嗎? 對于兩個長度均為 $n$ 的序列 $A$ 和 $B$,當且僅當存在一個正整數 $x$,滿足以下條件時,我們說序列 $A$ 的字典序小于 $B$。
- 對于任意正整數 $1≤i<x$,序列 $A$ 的第 $i$ 個元素 $A_i$ 和序列 $B$ 的第 $i$ 個元素 $B_i$ 相同。
- 序列 $A$ 的第 $x$ 個元素的值小于序列 $B$ 的第 $x$ 個元素的值。
輸入輸出格式
輸入格式
輸入文件共 $m+1$ 行。第一行包含兩個整數 $n,m(m≤n)$,中間用一個空格分隔。
接下來 $m$ 行,每行包含兩個整數 $u,v(1≤u,v≤n)$ ,表示編號為 $u$ 和 $v$ 的城市之間有一條道路,兩個整數之間用一個空格分隔。
輸出格式
輸出文件包含一行,$n$ 個整數,表示字典序最小的序列。相鄰兩個整數之間用一個 空格分隔。
輸入輸出樣例
輸入樣例#1
6 5
1 3
2 3
2 5
3 4
4 6
輸出樣例#1
1 3 2 5 4 6
輸入樣例#2
6 6
1 3
2 3
2 5
3 4
4 5
4 6
輸出樣例#2
1 3 2 4 5 6
說明
【數據規模與約定】
對于 $100\%$ 的數據和所有樣例, $1 \le n \le 5000$ 且 $m = n ? 1$ 或 $m = n$。
對于不同的測試點, 我們約定數據的規模如下:
?
估計是 $Day1$ 難度的原因,$Day2$ 上來就送了大禮包——怎么 $D2T1$ 就可以出有難度的題了!……
嗷,原來 $n=m$ 的簡單連通圖叫基環樹啊(“基”就是簡單、$simple$、一個的意思),我甚至還不知道。
?
題目意思不要看錯了……如果是一棵樹,你經過一條邊再回來之后,就不能再經過這條邊了。
也就是說你經過了這條邊,就要把這條邊下面的子樹跑完再回來。
根據字典序的性質,前面的數越小越好,所以優先跑編號小的兒子就最優了,排個序就可以。
?
那如果是基環樹呢?可以刪一條邊,因為我們發現環上一定有一條邊是不用經過的。
那哪一條邊不用經過呢?
方法1:枚舉
就是枚舉刪去哪條邊,然后跑一遍剩下的樸素樹即可。時間復雜度 $O(n^2\times log(n))$。
然而這個時間會被卡,我們發現不用每次跑樹時都進行排序,所以可以預處理每個點的所有兒子排序后的順序。時間復雜度 $O(n\times log(n)+n^2)$。其實這個在考場上是最穩的……
方法2:use your brain to find it
我們知道,環的根(就是整棵樹的根到環上距離最近的那個點)一定與環上的兩個兒子連接,設小的那個為 $x$,那么在貪心遍歷這個環時(即從根往編號小的兒子那邊走),遇到的第一個編號 $\gt x$ 的點就停下,因為新到這個點 不如退回去新到那個編號為 $x$ 的點更優,所以下一步不應該走這個點,刪掉它的父邊就可以了(別忘了無向圖要刪兩條有向邊)。
但是考后發現,這個做法是錯的,卻艸了官方數據的 $92$ 分……
錯的原因是,它沒有考慮環上長的毛的影響。
比如這樣一個圖
?
按照如上做法,從環的根($1$ 號點)跑到 $2$ 號點,因為 $2$ 號店在環上的兒子 $4$ 比 $1$號點在環上的另一個兒子(沒跑的那個)$3$ 號點小,所以會斷掉 $2,4$ 號點的連邊。
但實際上這樣做不對,因為 如果 $2$ 號點下一步不走 $4$ 號點,那它得先走比 $4$ 號點大的 $5$ 號點(因為 $5$ 號點是環長出來的毛,也就是一棵樹的根節點,所以必須要跑完這棵樹才能回去),顯然更不優。
所以更正上述算法,把環上那個兒子點 跟已經跑的環路徑上的(包括當前點) 上一個長了比環路徑上同層點更大的毛的點的 最小的一個?比較(因為如果割掉當前點與環上兒子點的連邊,下一個新到的點就是上面那一堆黑字所述的點,也就是說兩種路徑的字典序大小 就取決于上述兩點的大小了)。
理論時間復雜度為 $O(n\times log(n))$,但實際上對樹上每個點的兒子排序的時間復雜度是略超過這個的,因為分批的 $log$ 級別排序的復雜度 $>$ 所有點的 $log$ 級別排序的復雜度,大概就是 $2$ 的次方數越大增長越快的緣故。若把排序改為基數排序可把復雜度降至 $O(n)$ 帶點常數(我沒寫)。
1 #include<bits/stdc++.h> 2 #define N 500005 3 using namespace std; 4 inline int read(){ 5 int x=0; bool f=1; char c=getchar(); 6 for(;!isdigit(c);c=getchar()) if(c=='-') f=0; 7 for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0'); 8 if(f) return x; 9 return 0-x; 10 } 11 int n,m; 12 struct edge{int v,nxt;}e[N<<1]; 13 int head[N],cnt; 14 inline void add(int u,int v){e[++cnt]=(edge){v,head[u]}, head[u]=cnt;} 15 bool inq[N],vis[N],delVis[N]; 16 int f[N]; 17 int futa_u,futa_v,del1,del2; 18 int Min=2147483647,End=-2147483647,SecWay=2147483647; 19 20 void delEdge(int u,int fa){ 21 if(u==End){ 22 for(int i=head[u];i;i=e[i].nxt){ 23 del1=i; 24 if(del1&1) del2=del1+1; 25 else del2=del1-1; 26 return; 27 } 28 } 29 int tmpMin=2147483647, tmpE; 30 bool out=0; 31 for(int i=head[u];i;i=e[i].nxt) 32 if(e[i].v^fa && delVis[e[i].v]){tmpMin=e[i].v, tmpE=i; break;} 33 for(int i=head[u];i;i=e[i].nxt){ 34 if(e[i].v^fa && !delVis[e[i].v] && e[i].v>tmpMin){ 35 if(!out) out=1, SecWay=2147483647; 36 SecWay=min(SecWay,e[i].v); 37 } 38 } 39 //printf("%d %d %d\n",u,tmpMin,SecWay); system("pause"); 40 if(tmpMin>=SecWay){ 41 //printf("%d %d %d\n",u,tmpMin,SecWay); 42 del1=tmpE; 43 if(del1&1) del2=del1+1; 44 else del2=del1-1; 45 return; 46 } 47 delEdge(tmpMin,u); 48 } 49 bool gotR(int u,int fa){ 50 //printf("%d\n",u); 51 if(vis[u]){ 52 //printf("faq\n"); 53 int x=fa, go=0; 54 delVis[u]=1; 55 while(x!=u) delVis[x]=1, x=f[x], ++go; 56 57 if(go>1){ 58 for(int i=head[u];i;i=e[i].nxt) if(delVis[e[i].v]){ 59 //printf("%d\n",e[i].v); 60 Min=min(Min,e[i].v), End=max(End,e[i].v); 61 }//printf("root:%d\n",u); 62 SecWay=End; 63 delEdge(Min,u); 64 } 65 return 1; 66 } 67 vis[u]=1, f[u]=fa; 68 for(int i=head[u];i;i=e[i].nxt) 69 if(e[i].v^fa){ 70 //printf("set_fa:%d %d %d\n",u,e[i].v,u); 71 if(gotR(e[i].v,u)) return 1; 72 //printf("faq\n"); 73 } 74 return 0; 75 } 76 int v[N]; 77 void dfs(int u,int fa,int l,int r){ 78 if(u==1) printf("1"); 79 else printf(" %d",u); 80 for(int i=head[u];i;i=e[i].nxt) 81 if(e[i].v^fa && del1^i && del2^i) 82 v[++r]=e[i].v; 83 sort(v+l,v+r+1); 84 for(int i=l;i<=r;++i) dfs(v[i],u,r+1,r); 85 } 86 //set<pair<int,int> >s; 87 int main(){ 88 //freopen("testdata.in","r",stdin); 89 //freopen("travel.out","w",stdout); 90 n=read(),m=read(); 91 int i,u,v; 92 for(i=1;i<=m;++i){ 93 u=read(),v=read(),add(u,v),add(v,u); 94 } 95 if(m==n) gotR(1,0); 96 dfs(1,0,1,0); 97 return 0; 98 } View Code?
Extra:某些省的某些大佬寫的樸素樹的做法
?
5. 填數游戲
此題征集規律證明,如有知道的大佬請在評論里回復,謝謝^_^
?
6. 保衛王國
題目描述
Z 國有 $n$ 座城市,$n?1$ 條雙向道路,每條雙向道路連接兩座城市,且任意兩座城市都能通過若干條道路相互到達。
Z 國的國防部長小 Z 要在城市中駐扎軍隊。駐扎軍隊需要滿足如下幾個條件:
- 一座城市可以駐扎一支軍隊,也可以不駐扎軍隊。
- 由道路直接連接的兩座城市中至少要有一座城市駐扎軍隊。
- 在城市里駐扎軍隊會產生花費,在編號為i的城市中駐扎軍隊的花費是 $p_i$。
在城市里駐扎軍隊會產生花費,在編號為i的城市中駐扎軍隊的花費是 $p_i$。
小 Z 很快就規劃出了一種駐扎軍隊的方案,使總花費最小。但是國王又給小 Z 提出 了 $m$ 個要求,每個要求規定了其中兩座城市是否駐扎軍隊。小 Z 需要針對每個要求逐一給出回答。具體而言,如果國王提出的第 $j$ 要求能夠滿足上述駐扎條件(不需要考慮第 $j$ 個要求之外的其它要求),則需要給出在此要求前提下駐扎軍隊的最小開銷。如果國王提出的第 $j$ 個要求無法滿足,則需要輸出 $-1(1≤j≤m)$。現在請你來幫助小 Z。
輸入描述
第 $1$ 行包含兩個正整數 $n,m$ 和一個字符串 $type$,分別表示城市數、要求數和數據類型。$type$ 是一個由大寫字母 $A$,$B$ 或 $C$ 和一個數字 $1$,$2$,$3$ 組成的字符串。它可以幫助你獲得部分分。你可能不需要用到這個參數。這個參數的含義在【數據規模與約定】中 有具體的描述。
第 $2$ 行 $n$ 個整數 $p_i$,表示編號 $i$ 的城市中駐扎軍隊的花費。
接下來 $n?1$ 行,每行兩個正整數 $u,v$,表示有一條 $u$ 到 $v$ 的雙向道路。
接下來 $m$ 行,第 $j$ 行四個整數 $a,x,b,y(a≠b)$,表示第 $j$ 個要求是在城市 $a$ 駐扎 $x$ 支軍隊, 在城市 $b$ 駐扎 $y$ 支軍隊。其中,$x$、$y$ 的取值只有 $0$ 或 $1$:若 $x$ 為 $0$,表示城市 $a$ 不得駐扎軍隊,若 $x$ 為 $1$,表示城市 $a$ 必須駐扎軍隊;若 $y$ 為 $0$,表示城市 $b$ 不得駐扎軍隊,若 $y$ 為 $1$,表示城市 $b$ 必須駐扎軍隊。
輸入文件中每一行相鄰的兩個數據之間均用一個空格分隔。
輸出描述
輸出共 $m$ 行,每行包含 $1$ 個整數,第jj行表示在滿足國王第 $j$ 個要求時的最小開銷, 如果無法滿足國王的第 $j$ 個要求,則該行輸出 $-1$。
輸入輸出樣例
輸入樣例#1
5 3 C3
2 4 1 3 9
1 5
5 2
5 3
3 4
1 0 3 0
2 1 3 1
1 0 5 0
輸出樣例#1
12
7
-1
說明
【樣例解釋】
對于第一個要求,在 $4$ 號和 $5$ 號城市駐扎軍隊時開銷最小。
對于第二個要求,在 $1$ 號、$2$ 號、$3$ 號城市駐扎軍隊時開銷最小。
第三個要求是無法滿足的,因為在 $1$ 號、$5$ 號城市都不駐扎軍隊就意味著由道路直接連接的兩座城市中都沒有駐扎軍隊。
【數據規模與約定】
對于 $100\%$ 的數據,$n,m≤100000, 1≤p_i≤100000$。
數據類型的含義:
$A$:城市 $i$ 與城市 $i+1$ 直接相連。
$B$:任意城市與城市 $1$ 的距離不超過 $100$(距離定義為最短路徑上邊的數量),即如果這棵樹以 $1$ 號城市為根,深度不超過 $100$。
$C$:在樹的形態上無特殊約束。
$1$:詢問時保證 $a=1,x=1$,即要求在城市 $1$ 駐軍。對 $b,y$ 沒有限制。
$2$:詢問時保證 $a,b$ 是相鄰的(由一條道路直接連通)
$3$:在詢問上無特殊約束。
?
某些人表示見過動態 $dp$ 板子……
然后就發現這題其實就是動態 $dp$ 板子題的弱化版(不帶修改)。
然后就不會打動態 $dp$,陷入自閉
?
然而,還是那句話,$noip$ 的官方正解不會是一些太變態的內容的(不過我個人為打表找規律就很變態)。
所以這題是倍增 $dp$(動態 $dp$ 的做法后面也會說)。
不過這題的無解很好判,只有強制一條邊連接的兩個點同時不駐防時,才會無解。
?
前44pts:
對于每組詢問暴力重做一遍 $dp$。
1 #include<cmath> 2 #include<cstdio> 3 #include<cstdlib> 4 #include<cstring> 5 #include<iostream> 6 #include<algorithm> 7 #define N 2005 8 const long long inf=300010ll*100010; 9 using namespace std; 10 inline int read(){ 11 int x=0; bool f=1; char c=getchar(); 12 for(;!isdigit(c);c=getchar()) if(c=='-') f=0; 13 for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0'); 14 if(f) return x; 15 return 0-x; 16 } 17 int n,m,p[N],a,x,b,y; char ch[5]; bool Mat[N][N]; 18 struct edge{int v,nxt;}e[N<<1]; 19 int head[N],cnt; 20 inline void add(int u,int v){e[++cnt]=(edge){v,head[u]}, head[u]=cnt;} 21 long long dp[N][2]; 22 23 void DP(int u,int fa){ 24 //printf("%d %d\n",u,fa); 25 for(int i=head[u];i;i=e[i].nxt) if(e[i].v^fa) DP(e[i].v,u); 26 if(!(u==a && x==0) && !(u==b && y==0)){ 27 long long jia; 28 for(int i=head[u];i;i=e[i].nxt) 29 if(e[i].v^fa){ 30 jia=min(dp[e[i].v][0],dp[e[i].v][1]); 31 if(jia<inf) dp[u][1]+=jia; //考后才發現,事實上可以證明 只有下面相鄰兩個點都被禁止駐軍時才會出現這種情況,但這種情況在dp前已經判過了 32 } 33 dp[u][1]+=p[u]; 34 //printf("1 %d:%d\n",u,dp[u][1]); 35 } 36 else dp[u][1]=inf; 37 if(!(u==a && x==1) && !(u==b && y==1)){ 38 for(int i=head[u];i;i=e[i].nxt) 39 if(e[i].v^fa) dp[u][0]+=dp[e[i].v][1]; 40 //printf("0 %d:%d\n",u,dp[u][0]); 41 if(dp[u][0]>inf) dp[u][0]=inf; 42 } 43 else dp[u][0]=inf; 44 } 45 int main(){ 46 //freopen("defense.in","r",stdin); 47 //freopen("defense.out","w",stdout); 48 n=read(),m=read(); 49 scanf("%s",ch); 50 int i,j,u,v; 51 for(i=1;i<=n;++i) p[i]=read(); 52 for(i=1;i^n;++i) u=read(),v=read(),Mat[u][v]=Mat[v][u]=1,add(u,v),add(v,u); 53 while(m--){ 54 a=read(),x=read(),b=read(),y=read(); 55 if(Mat[a][b] && x==0 && y==0) printf("-1\n"); 56 else{ 57 memset(dp,0,sizeof dp); 58 DP(1,0); 59 printf("%lld\n",min(dp[1][0],dp[1][1])); 60 } 61 } 62 return 0; 63 } 44分暴力?
B1 8pts(這部分我在考場上直接沒看見,太虧了):
不難發現,強制兩點的狀態 只可能對兩點的所有祖先的 $dp$ 值有影響,像一個倒 $y$ 字形。
而 $B$ 的限制是樹高不超過 $100$,所以我們可以暴力改這些祖先的 $dp$ 值。
實際上,由于還有 $1$ 的限制,有一個修改點必定是根,所以只需要改一條鏈就行了。
?
A 24pts(樹是鏈):
?
100pts:
方法1:倍增dp
可以發現,
?
LCT做法
后記
轉載于:https://www.cnblogs.com/scx2015noip-as-php/p/noip2018.html
總結
以上是生活随笔為你收集整理的noip2018——题解总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springboot的笔记
- 下一篇: HashMap构造函数有哪些