1. 程式人生 > >NOIP2013 提高組複賽解題報告

NOIP2013 提高組複賽解題報告

NOIP2013 提高組複賽

day1
這裡寫圖片描述

1002. 火柴排隊

  • 貪心+資料結構/歸併排序

這個“相鄰交換”讓我聯想到了NOIP2012_day1_task2_game那題的噁心做法,於是就專注推導相鄰兩個元素交換對解的影響。然後根據以前經驗知道一定有一個序列可以完全不動,而另一個序列只需要以第一列作為標準移動(所以樣例解釋反而給的誤導很大)。於是很快就確信正解了。

現在想起來還有一點小激動,自己居然能找出規律來。

首先,一定可以只對某一組的元素進行交換,能以給定的最少步數,得到另外一組的結果。顯然當前序列還缺少k步就和另外一個序列相同時,另外一個序列一定也只需要k步就可以和當前序列相同。

其次,由於只能相鄰交換,對於每一次的操作,它對於解的影響一定只與這一對元素有關。於是我們對相鄰元素的數量關係進行推導:

假設有兩組元素ai,ai+1bi,bi+1,如果有

(aibi)2+(ai+1bi+1)2<(aibi+1)2+(ai+1bi)2

則說明有{bi,bi+1}的順序比{bi+1,bi}更優。對上述式子推導有:

2aibi+1+2ai+1bi<2aibi+2ai+1bi+1ai(bi+1bi)+ai+1(bibi+1)<0(aiai+1)(bibi+1)>0

顯然上式滿足的唯一情況就是ai>ai+1,bi>

bi+1ai<ai+1,bi<bi+1。也就是說,要求兩個序列的元素大小單調且對應。於是我們只要依次配對a序列最大,次大…的位置和b序列最大,次大…的位置即可。

我們對於a序列中元素,在b序列中找到它應當對應的元素在b序列的位置。也就是對於a序列中1,2,3,4…的順序,到b序列中就成了1~n的全排列。於是問題就轉化成要將b序列的這個排列有序的最少相鄰交換次數。

於是我們想到了氣泡排序和逆序對,隨便求求逆序對的個數就可以了。採用歸併排序完成的方法和資料結構統計的方法都是可以的。

Code:

#include <cstdio>
#include <cstring>
#include <algorithm> #define M 100005 #define P 99999997 using namespace std; template <class temp> inline void Rd(temp &res){ res=0;char c; while(c=getchar(),c<48); do res=(res<<3)+(res<<1)+(c^48); while(c=getchar(),c>47); } int n,a[M],b[M],p[M],q[M]; long long cnt=0; bool cmp(int a1,int a2){return b[a1]<b[a2];} bool _cmp(int p1,int p2){return a[p1]<a[p2];} void Merge(int L,int R){ if(L==R)return; int mid=L+R>>1; Merge(L,mid),Merge(mid+1,R); int low=L,high=mid+1,tot=L; while(low<=mid&&high<=R) if(b[low]<b[high])p[tot++]=b[low++]; else{ cnt+=mid-low+1; p[tot++]=b[high++]; } while(low<=mid)p[tot++]=b[low++]; while(high<=R)p[tot++]=b[high++]; for(int i=L;i<=R;i++)b[i]=p[i]; } int main(){ Rd(n); for(int i=1;i<=n;i++)Rd(a[i]),q[i]=i; for(int i=1;i<=n;i++)Rd(b[i]),p[i]=i; sort(q+1,q+n+1,_cmp); sort(p+1,p+n+1,cmp); for(int i=1;i<=n;i++)b[p[i]]=q[i]; Merge(1,n); cnt%=P; printf("%d\n",(int)cnt); }

1003. 火車運輸

  • 並查集+啟發式合併
  • 生成樹+LCA
  • 離線+整體二分

現在遇到這種走路徑限值的題目就非常害怕。因為標程幾乎與Dijkstra那種圖論演算法根本搭不上邊……寫這種題目的時候應當將每個點作為集合,或者說集合中的元素來看,然後再採用合併集合的思想進行考慮。

根據本題還要總結一個教訓:對於任何圖論題,一定要儘可能將其轉化為樹論題。因為樹的性質和可行操作遠遠比圖要多,處在一個圖為樹的環境下,顯然思路會更加廣闊。

無論如何,這題都是一道大寫的好題。

可能考慮對於每一個詢問,我們都跑一遍單源最短路演算法,並且要求從經過邊權儘可能大的點來轉移。在此基礎上進行少許優化,期望得分只有30分。

之後由於要求路徑上“最小值最大”,於是我們考慮二分的做法。保留所有不小於列舉的最小值的邊,再判斷需要的路徑是否連通即可。複雜度來說,對於q個詢問,每次都需要O(logm)的二分,然後判斷圖連通需要O(n),最後總時間複雜度為O(nqlogm)。顯然有待改進。

撇開這種做法不談,我們可以參照NOIP2012_day2_task2_classroom二分轉線性思路,按邊權從大到小列舉這個邊權。於是接下來只需要在不斷合併的過程中,詢問的路徑由於邊的不斷加入而趨向連通。當它第一次連通的時候,加入的邊權值就是這個詢問的答案了。合併操作我們一般採用並查集去完成。如果判定是否詢問的兩點連通採用直接for過來的方法,時間複雜度為O(mq×α(n)),期望得分60分。

實際上加入了一條邊就是將兩個端點所在的集合進行合併,我們可以在進行合併操作的時候順便處理掉詢問,然後再把沒有處理掉的兩個集合的詢問合併起來。顯然在極端情況下,如果我們把大集合向小集合進行合併,那麼大集合內掛的詢問被訪問的次數就是O(m)的,時間複雜度仍然為O(mq×α(n))

顯然我們可以改變這個合併的順序,使得每一個詢問的訪問次數都穩定在O(logm)。這就是所謂的啟發式合併了,它的操作就只是將小集合向大集合合併,但是這樣可以保證每個點被詢問到的複雜度在log級別。於是時間複雜度就降到O(qlogm×α(n))O(qlogm)。對於啟發式合併的證明和做法亦可以參考題目IOI2011_Race<