2811: [Apio2012]Guard Time Limit: 10 Sec Memory Limit: 128 MB
Submit: 673 Solved: 303
[ Submit][ Status][ Discuss] Description
Input
Output
Sample Input 5 3 4
1 2 1
3 4 1
4 4 0
4 5 1
Sample Output 3
5
HINT
在這個範例中,有兩種可能的安排方式:1,3,5 或者 2,3,5。即 3 和 5
後面必然躲著一個忍者。
考慮第一個灌木叢,存在一種安排方案使得它的後面躲著忍者,但也存在一
種安排方案使得它後面沒有躲忍者,因此不應該輸出 1。同理,不應該輸出 2。
Source
[ Submit][ Status][ Discuss]
碼了一個線段樹發現正解是貪心。。醉了,,果然我好弱啊。。好弱啊
對於某個位置,強制不放忍者,若能找到一種符合該條件的方案,使得需要忍者數小於等於k,那麼這個點就不一定需要忍者(因為多出來的忍者可以隨意放) 那麼就變成一個有限制的尋找最優解的問題,,如果能很快回答這個最優解是多少,那麼問題就解決了
對於一些根本不可能有忍者或者是輸入資料中不包含的點,直接刪掉 然後離散化,對應修改每個守衛看到的區間 對於任意兩個新的區間,可能存在包含的情況,顯然,如果小的滿足那麼大的一定滿足,貪心地只保留小的,這樣得到的最優解也還是全域最優解 這樣處理剩下的區間按照左端點排序,顯然滿足左右端點均單調遞增 對於剩下的區間中,有些點同時被多個區間包含,也就是說如果這個點放入忍者,那麼就能同時滿足多個區間,顯然,關鍵點一定是這樣的點 假設我們排序後從左往右處理這些區間,如果這個區間已經被前面的放置方案滿足,則跳過,否則我們每次貪心地將忍者放在區間的右端點,這樣得到的解顯然是最優的,那麼我們就定義每個區間最有價值的點為這個區間的右端點,而關鍵點只可能是每個區間的右端點
對於每一個剩下的區間,我們預先處理從左往右到這個區間覆蓋所需最少忍者數,已經最後一個忍者放在哪裡,同理從右往左試一遍(這時候就是優先放左端點了),這個用貪心瞎搞一下 那麼問題來了,,為什麼不考慮左端點。因為關鍵點必定同時被多個區間包含,且如果同時有多個區間的重疊部分也是一個區間的話,這個區間的所有點都不是關鍵點的可能性很大,也就是說,真正的關鍵點趨向於剛好是兩個區間只有端點相交的時候(感覺就是這樣,,不能給出很嚴肅的證明...)於是我們處理右端點就行了
既然已經證明了一個區間的最優點是右端點,那麼次優點應該就是右端點-1,二分一個k1,使得覆蓋到第k1區間時,最後放置的點小於r - 1,且k1盡量大 再二分一個k2,使得從右往左覆蓋到第k2個區間時,最後放置的點大於r - 1,(最後放置點是r也不影響,因為這時候這個區間長度一定大於1,也就是說我們可以當做這個點其實是放在r+1) 特判一下這時候能否解決覆蓋所有區間,如果不能,則r-1這個點也必須放一個忍者,這樣就是當前最優解了
寫的時候注意,,, 如果刪完區間剩下的長度剛好是k,可以直接全部輸出 如果一個區間長度為1,這個點一定是關鍵點
細節很多,,,是在寫的過程中慢慢總結的
#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std; const int maxn = 1E5 + 10;const int INF = ~0U>>1; int n,k,m,cnt,tot,ans,L[maxn],R[maxn],s1[maxn],Pre[maxn],s2[maxn],Min[maxn] ,Nex[maxn],Num[maxn],po[maxn],Max[maxn],tl[maxn],tr[maxn],Ans[maxn]; int getint(){ char ch = getchar(); int ret = 0; while (ch < '0' || '9' < ch) ch = getchar(); while ('0' <= ch && ch <= '9') ret = ret*10 + ch - '0',ch = getchar(); return ret;} int main(){ #ifdef DMC freopen("DMC.txt","r",stdin); #endif n = getint(); k = getint(); m = getint(); for (int i = 1; i <= m; i++) { int l,r,typ; l = getint(); r = getint(); typ = getint(); if (!typ) ++s1[l],--s1[r+1]; else { L[++cnt] = l; R[cnt] = r; ++s2[l]; --s2[r+1]; } } int rest = 0; for (int i = 1; i <= n; i++) { bool flag1,flag2; flag1 = flag2 = 0; s1[i] += s1[i-1]; if (!s1[i]) flag1 = 1,++rest; s2[i] += s2[i-1]; if (s2[i]) flag2 = 1; if (flag1 && flag2) { Num[i] = ++tot; po[tot] = i; } } if (rest == k) { for (int i = 1; i <= n; i++) if (!s1[i]) printf("%d\n",i); return 0; } for (int i = 1; i <= n; i++) Pre[i] = Num[i]?i:Pre[i-1]; for (int i = n; i; i--) Nex[i] = Num[i]?i:Nex[i+1]; for (int i = 1; i <= tot; i++) Min[i] = INF; for (int i = 1; i <= cnt; i++) { L[i] = Num[Nex[L[i]]]; R[i] = Num[Pre[R[i]]]; Min[L[i]] = min(Min[L[i]],R[i]); } cnt = 0; for (int i = 1; i <= tot; i++) { if (Min[i] == INF) continue; while (cnt && R[cnt] >= Min[i]) --cnt; L[++cnt] = i; R[cnt] = Min[i]; } Max[0] = -1; for (int i = 1; i <= cnt; i++) if (L[i] <= Max[i-1]) { tl[i] = tl[i-1]; Max[i] = Max[i-1]; } else { tl[i] = tl[i-1] + 1; Max[i] = R[i]; } Min[cnt+1] = INF; for (int i = cnt; i; i--) if (R[i] >= Min[i+1]) { tr[i] = tr[i+1]; Min[i] = Min[i+1]; } else { tr[i] = tr[i+1] + 1; Min[i] = L[i]; } for (int i = 1; i <= cnt; i++) { if (L[i] == R[i]) { Ans[++ans] = po[R[i]]; continue; } int sl,sr,l,r,tmp = R[i] - 1,A,B; l = 0; r = cnt + 1; while (r - l > 1) { int mid = (l + r) >> 1; if (Max[mid] >= tmp) r = mid; else l = mid; } if (Max[r] < tmp) sl = tl[r],A = r; else sl = tl[l],A = l; l = 0; r = cnt + 1; while (r - l > 1) { int mid = (l + r) >> 1; if (Min[mid] <= tmp) l = mid; else r = mid; } if (Min[l] > tmp) sr = tr[l],B = l; else sr = tr[r],B = r; if (B == i) ++B; int Add = A >= B - 1?0:1; if (sl + sr + Add > k) Ans[++ans] = po[R[i]]; } if (!ans) { cout << -1; return 0; } sort(Ans + 1,Ans + ans + 1); for (int i = 1; i <= ans; i++) printf("%d\n",Ans[i]); return 0;}
對於每一個剩下的區間,我們預先處理從左往右到這個區間覆蓋所需最少忍者數,已經最後一個忍者放在哪裡,同理從右往左試一遍(這時候就是優先放左端點了),這個用貪心瞎搞一下