發表文章

目前顯示的是有「greedy」標籤的文章

[ZeroJudge] e201: 期末打掃

題目連結: https://zerojudge.tw/ShowProblem?problemid=e201 稀有的過來寫個解。大學生活好累,每天都在被作業QAQ 一開始看錯題目了,不過因為沒看錯太多所以改個幾行就過了XD 看到這題我的第一個想法是樹DP,所以想說先想想看有沒有甚麼有用的性質。感受了一下發現我可以定義$f_u(x)$代表在$x$時間點進入$u$後再出來的時間點,那可以發現$f_u(x)=\max(a_{u0},x+d_u)$,其中$d_u$代表可以不用等待就都檢查完$u$的子樹的時間,$a_{u0}$代表如果是第0個時間點就從$u$出發那要多久後才能回到$u$並把所有教室都檢查完的時間。證明這部分成立的方法也很簡單(不過我很笨想了很久QAQ),假設在時間點$t\geq 0$,存在一種走法最佳,那我們可以知道他的時間一定$\geq t+d_u$,因為不管怎樣我們至少一定要把底下整棵子樹都走過一遍才能回來,另外我們也知道那個最佳走法的時間也$\geq a_{u0}$,因為如果他更小的話,我們在時間點$0$的時候就可以故意等到時間點$t$在走,這樣可以走出更加的解,與假設不合,這樣我們就證明了在任意時間點$t$的任意解$g$都有$g \geq f_u(t)$,而且明顯$f_u(t)$是可以達到的,因為我們可以照著$t=0$的路徑走,那如果中間很順利不用逗留的話就達到了$f_u(x)=x+d_u$,而若是要逗留的話,那在逗留的當下我們接著的時間就會跟$t=0$的時候同步了,所以這時$f_u(x)=a_{u0}$。 講了這麼多我們現在終於知道一個人在時間點$x$進入$u$後出來的時間,那接下來就是要決定當我們在$u$的時候該以怎樣的順序走訪他的子樹們$v$了,而這時我盯著$f_u(x)=\max(a_{u0},x+d_u)$的圖(一堆平移過後的 ReLU 函數w)一段時間後猜想順序就是按著$f_u$的轉折點的$x$座標由小到大拜訪就會是好的,也就是按照$a_{u0}-d_u$由小到大走訪就會是最佳的,不過這部分我證不太出來,所以後來就依靠 Z3 證明了對於任意$s,t,x_0\geq 0$都有$a_{s0} - d_s \leq a_{t0} - d_t \Rightarrow f_t(f_s(x_0)) \leq f_s(f_t(x_0))$,那我...

[TIOJ] 1975. 即時工作排程系統(Scheduler)

題目連結: https://tioj.infor.org/problems/1975 首先本題要可以觀察到對於只有即時性工作時我們很簡單的就是計算他重疊最多的部分是多少,而對於非即時性工作則是要透過由deadline由小到大排後,每次選取被加值最少的那些不重疊區間來分配,最後計算最大值。 合併這兩個思考之後就可以發現本題變成一個用treap可以維護的題目了XD #include <bits/stdc++.h> using namespace std; const int N = 100000 + 5; const int C = 1000000 + 5; namespace Treap{ #define sz( x ) ( ( x ) ? ( ( x )->size ) : 0 ) #define sm( x ) ( ( x ) ? ( ( x )->sum ) : 0 ) struct node{ int size, cnt, sum; uint32_t pri; node *lc, *rc; node(): size( 0 ), cnt( 0 ), sum( 0 ), pri( rand() ), lc( nullptr ), rc( nullptr ) {} node( int x ): size( 1 ), cnt( x ), sum( x ), pri( rand() ), lc( nullptr ), rc( nullptr ) {} void pull() { sum = cnt; if ( lc ) sum += lc->sum; if ( rc ) sum += rc->sum; size = 1; if ( lc ) size += lc-...

[Codeforces] 675C. Money Transfers

題目連結: http://codeforces.com/problemset/problem/675/C 想了很久才知道方向錯了QAQ 作法是枚舉每個人往右邊推過去(可以證明只要枚舉所有人往右走就一定是好的),那仔細觀察就會發現我們要省幾個就是看我們走過來有幾個前綴為零,而枚舉每一個人做開頭的時候其實我們就是問一直把最後的放到前面,也就是把前面加值,最後一個減值接著再查一下有幾個0就好了XD #include <bits/stdc++.h> using namespace std; typedef long long lld; const int N = 100000 + 5; map<lld,int> cnt; lld arr[N]; int main(int argc, char* argv[]){ ios_base::sync_with_stdio(0);cin.tie(0); int n; cin>>n; lld cur = 0; for(int i=0;i<n;i++){ cin>>arr[i]; cur += arr[i]; cnt[cur]++; } int ans = n-cnt[0]; cnt[0]--; for(int i=n-1;i>=0;i--){ cur -= arr[i]; ans = min(ans, n-cnt[cur]); } cout<<ans<<'\n'; return 0; }

[TIOJ] 1221. 炒菜問題 / [POI] XII. Toy Cars

題目連結: http://tioj.infor.org/problems/1221 或 POI XII Toy Cars 我不知道怎麼證明QAQ... 總之做法就是要移掉東西的時候,就挑接下來最晚出現的移掉。而要維護誰最晚出現我這裡是開個陣列先預處理好對於每個$i$,紀錄下一個跟他一樣的數字是在哪裡。而接著掃過去的時候,每走到一個地方,就把這個地方的更新掉,這要樣pop東西的時候就可以拿到最新的資訊了。 #include <bits/stdc++.h> using namespace std; #define PB push_back #define FF first #define SS second const int N = 500000 + 5; const int M = 100000 + 5; map<int,int> mp; int nxt[N], tmp[M], arr[N]; bool ins[M]; int main(){ ios_base::sync_with_stdio(0);cin.tie(0); int n, k, p; cin>>n>>k>>p; for(int i=0;i<p;i++) nxt[i] = p+i; for(int i=0;i<=n;i++) tmp[i]=-1; for(int i=0;i<p;i++) cin>>arr[i]; for(int i=0;i<p;i++){ if(tmp[arr[i]]!=-1) nxt[tmp[arr[i]]] = i; tmp[arr[i]]=i; } int size = 0, ans = 0; for(int i=0;i<p;i++){ if(!ins[arr[i]]){ if(size == k){ ...

[Codeforces] 731D. 80-th Level Archeology

題目連結: http://codeforces.com/problemset/problem/731/D 我覺得我的作法感覺不太對(?但還是講一下好了 我的做法是先把大小關係建成一張DAG,而這樣的話會發現題目變成說可不可以找到一種拓樸排序方法使得序列會是循環的字串,而又已經知道循環其實就只有可能中間斷開一個而已,所以我們不妨找到中間斷開的那個關係,並追本朔源到最前面的那個,從他開始拓樸排序,看會不會是好的。 #include <bits/stdc++.h> using namespace std; #define PB push_back #define FF first #define SS second const int N = 500000 + 5; const int C = 1000000 + 5; class DJS{ private: vector<int> arr; public: inline void init(int n){ arr.resize(n); for(int i=0;i<n;i++) arr[i]=i; } int query(int x){ if(arr[x]!=x) arr[x] = query(arr[x]); return arr[x]; } void merge(int a, int b){ int u = query(a), v = query(b); arr[max(u, v)]=min(u, v); } } djs; vector<int> G[C], cur; int in[C]; inline void kill(){cout<<"-1\n...

[Codeforces] 761E. Dasha and Puzzle

題目連結: http://codeforces.com/problemset/problem/761/E 一開始以為邊長只能是一www 會發現節點才30個,那我們可以簡單的安排每個深度的邊長$2^{55-d_i}$這樣的話,因為$\sum_{i=0}^{n-1}2^i #include <bits/stdc++.h> using namespace std; typedef long long lld; #define PB push_back typedef pair<lld,lld> PLL; #define FF first #define SS second const int N = 30 + 5; lld dx[]={0, 0, 1, -1}, dy[]={1, -1, 0, 0}; int inv[]={1, 0, 3, 2, 5}; vector<int> G[N]; PLL ans[N]; int dep[N]; void dfs(int,int,int,PLL); int main(){ ios_base::sync_with_stdio(0);cin.tie(0); int n; cin>>n; for(int i=0;i<n-1;i++){ int u, v; cin>>u>>v; G[u].PB(v); G[v].PB(u); } bool flag = false; for(int i=1;i<=n;i++) if(G[i].size() > 4){ flag = true; } if(flag){ cout<<"NO\n"; return 0; } cout<<...

[TIOJ] 1610. Problem D 搭橋 (BRIDGE)

題目連結: http://tioj.infor.org/problems/1610 乍看之下還以為是什麼奇怪的樹題,結果其實是個簡單的greedy題。 應該不難看出我們其實可以把船亂排、木板亂排,而答案會是$\sum_{i=0}^{n-1} L_i \sum_{j=0}^{i} W_j$,稍微觀察後會發現其實我們由小拿到到,然後木板由長的接到短的會是好的,因為你若拿了比較多再走長的一定會消耗較多體力。 #include <bits/stdc++.h> using namespace std; typedef long long lld; const int N = 20000 + 5; lld arr[N], woo[N]; int main(){ ios_base::sync_with_stdio(0);cin.tie(0); int n; cin>>n; for(int i=0;i<n;i++) cin>>arr[i]; for(int i=1;i<n;i++) cin>>woo[i]; sort(arr, arr+n); sort(woo+1, woo+n, [](lld a, lld b){return a>b;}); lld cur = arr[0], ans = 0; for(int i=1;i<n;i++){ ans += cur*woo[i]; cur += arr[i]; } cout<<ans<<'\n'; return 0; }

[Codeforces] 864E. Fire

題目連結: http://codeforces.com/problemset/problem/864/E 只感覺應該是DP,而且東東的順序會有影響,但是完全不知道該用何種順序來DP,看了tutorial才知道原來要按照消失時間來DP。 tutorial中其實並未給出證明,不過下面有人表示「如果你不先挑那些會先消失的人,那很有可能他待會就消失了(當然挑他要是有助益的)」,想了想我自己是覺得說,如果你可以先挑比較晚消失的人,再挑比較早消失的人,那你也一定可以先挑早消失再挑晚消失的,但反過來的挑法就不一定了。 那所以就按照那個順序排好序後,這題就變得類似於背包問題了,所以我們就可以跟背包一樣,用加入東東的方是想,每次加入一個物品就是在那些可行的範圍中往後加值之類的。 #include <bits/stdc++.h> using namespace std; const int N = 100 + 5; const int M = 2000 + 5; struct Obj{ int t, d, p; int id; inline bool operator<(const Obj &x) const { return d < x.d; } } arr[N]; int sum[M]; vector<int> ori[M]; int main(){ ios_base::sync_with_stdio(0);cin.tie(0); int n; cin>>n; for(int i=0;i<n;i++){ cin>>arr[i].t>>arr[i].d>>arr[i].p; arr[i].id = i+1; } sort(arr, arr+n); for(int i=0;i<n;i++){ for(int j=arr[i].d-1;j>...

[Codeforces] 802M. April Fools' Problem (easy)

題目連結: http://codeforces.com/problemset/problem/802/M 沒敘述的題目www 但其實應該不難猜到就是輸出前k小數字的和。 #include <bits/stdc++.h> using namespace std; int main(){ ios_base::sync_with_stdio(0);cin.tie(0); priority_queue<int,vector<int>,greater<int>> pq; int n, k; cin>>n>>k; for(int i=0;i<n;i++){ int x; cin>>x; pq.push(x); } int ans = 0; for(int i=0;i<k;i++){ ans += pq.top(); pq.pop(); } cout<<ans<<'\n'; return 0; }

[TIOJ] 1999. 排隊買飲料

題目連結: http://tioj.infor.org/problems/1999 裸著做(?,每次都從當前最早服務完的人中叫他來服務客人,那他服務完的時間就再加上服務這個人的時間。而要早當前最早服務完的就拿個priority_queue就好了XD #include <bits/stdc++.h> using namespace std; int main(){ ios_base::sync_with_stdio(0);cin.tie(0); int n, m; cin>>n>>m; priority_queue<int,vector<int>,greater<int>> pq; for(int i=0;i<m;i++) pq.push(0); for(int i=0;i<n;i++){ int x;cin>>x; int tp = pq.top();pq.pop(); pq.push(tp+x); } int ans = 0; while(!pq.empty()){ ans=pq.top();pq.pop(); } cout<<ans<<'\n'; return 0; }

[Codeforces] 698B. Fix a Tree

題目連結: http://codeforces.com/problemset/problem/698/B 一開始完全沒想法,被提示(?說就算非法也還是一張圖後才比較有想法。這題我的作法很greedy,就先判斷一下有沒有原本就有可以當根的點,有的話就把他當根,接下來再掃一遍,看看有沒有人會構成環,有的話就直接把他接到根上面,此外,若沒有現成的根的話就直接把他變成根。而要找誰跟誰一樣就用個並查集就好了XD #include <bits/stdc++.h> using namespace std; #define PB push_back #define FF first #define SS second const int N = 200000 + 5; class DJS{ private: vector<int> arr; public: void init(int n){ arr.resize(n); for(int i=0;i<n;i++) arr[i]=i; } int query(int x){ if(arr[x]!=x) arr[x] = query(arr[x]); return arr[x]; } void merge(int a, int b){ arr[query(a)]=arr[query(b)]; } } djs; int arr[N]; int main(){ ios_base::sync_with_stdio(0);cin.tie(0); int n, ans = 0, root = -1; cin>>n; for(int i=1;i<=n;i++){ cin...

[TIOJ] 1401. 功夫城堡

題目連結: http://tioj.infor.org/problems/1401 我好廢QQ被雷了才會。 很簡單的可以觀察到垂直跟水平可以分開,那這樣的話問題被轉化成給你一堆線段,你要為每條線段選一個點,使得這些點不能重複。做法很greedy直接從左掃到右邊,然後如果有可以放的就放,然後如果遇到一條線段就把他右界放進去之類的。 #include <bits/stdc++.h> using namespace std; #define PB push_back const int N = 100000 + 5; vector<int> hen[N], zhi[N]; int main(){ ios_base::sync_with_stdio(0);cin.tie(0); int n; while(cin>>n){ for(int i=1;i<=n;i++){ hen[i].clear(); zhi[i].clear(); } bool flag = true; for(int i=0;i<n;i++){ int l, r, u, d; cin>>l>>r>>u>>d; hen[l].PB(r); zhi[u].PB(d); } priority_queue<int,vector<int>,greater<int>> pq; for(int i=1;i<=n;i++){ for(auto j: hen[i]) pq.push(j); if(!pq.empty()){ if(pq.top() <...

[Codeforces] 198E. Gripping Story

題目連結: http://codeforces.com/contest/198/problem/E 被雷了才會QQ 會發現其實他是個圓並不重要,重要的其實是距離,所以不妨把每個人的座標轉換成跟原點的距離,這樣的話每次詢問就變成詢問一個距離內質量小於k的數有誰,而我們其實也不用一次把一坨東西拉出來,只要一次拉一個就好,反正最多拉n個,複雜度不會太慘。而這樣其實就是詢問一個前綴極值就可達到這件事,所以就開個BIT套個單調的queue之類的就可以做到這件事了。 #include <bits/stdc++.h> using namespace std; typedef long long lld; #define PB push_back #define ALL(x) begin(x), end(x) #define FF first #define SS second const int N = 250000 + 5; const lld INF = 1LL<<31; class LiSan{ private: vector<lld> vv; public: inline void init(){vv.clear();} inline void insert(lld x){vv.PB(x);} inline void done(){ sort(ALL(vv)); vv.resize(distance(vv.begin(), unique(ALL(vv)))); } inline int size(){return vv.size();} inline int get(lld x){ return distance(vv.begin(), lower_bound(ALL(vv), x)); } inline lld ...

[Codeforces] 732D. Exams

題目連結: http://codeforces.com/problemset/problem/732/D 一開始想爛了,但大方向還算對,原本以為可以直接greedy算,但一直算不太出來(雖然好像還是可)。後來發現其實可以對答案二分搜,然而驗答案的地方我一直寫爛掉,導致這題寫了超久了,但最後AC code其實也沒多複雜,真搞不懂哪裡爛掉。 稍微講一下驗證答案的部分好了,就因為有個簡單的性質就是你若可以今天考,但今天不考之後再考也可以,所以驗證答案的時候都讓每科考試都在最後一次可以考時才考,這樣前面就會有一堆溫書假了。 #include <bits/stdc++.h> using namespace std; const int N = 100000 + 5; int need[N], ok[N]; bitset<N> done; bool isOK(int,int); int main(){ ios_base::sync_with_stdio(0);cin.tie(0); int n, m; cin>>n>>m; for(int i=1;i<=n;i++) cin>>ok[i]; for(int i=1;i<=m;i++) cin>>need[i]; int l=1, r=n+1; while(r-l > 1){ int mid = (l+r)>>1; if(isOK(mid, m)) r=mid; else l=mid; } cerr<<ok[r]<<"\n"; if(r==n+1) cout<<"-1\n"; else cout<<r<<'\n'; return 0; } bool isOK(int n, int m){ ...

[Codeforces] 839B. Game of the Rows

題目連結: http://codeforces.com/problemset/problem/839/B 第一次會想把Div2的pB拿來放在這XD,這題實在有夠難寫的雖然一眼就知道他是greedy,但是思路要是不夠清楚基本上就會爛掉(這場CF我就這題炸掉了QAQ....)。Greedy的想法還算簡單,只要讓人數多的集團先坐,而且優先先把四人座坐滿即可(因為四人座若要做兩組人,中間一定會少一格(不能當作2格跟2格),接下來再做兩人座的部分。這時可能會剩一些座位空著就在想盡辦法把所有人都塞進,這裡一樣先把多的人塞到4人座裡面。 #include <bits/stdc++.h> using namespace std; int seat[5], arr[10000 + 5]; int main(){ int n, k; cin>>n>>k; seat[4]=n; seat[2]=2*n; for(int i=0;i<k;i++) cin>>arr[i]; sort(arr, arr+k, [](int a, int b){return a>b;}); for(int i=0;i<k;i++){ int qq = min(arr[i]/4, seat[4]); arr[i] -= qq*4; seat[4]-=qq; } sort(arr, arr+k, [](int a, int b){return a>b;}); for(int i=0;i<k;i++){ int qq = min(arr[i]/2, seat[2]); arr[i] -= qq*2; seat[2]-=qq; } priority_queue<int> pq; for(int i=0;i<k;i++) pq.push(arr[i])...

[AtCoder] ARC 080 E: Young Maids

題目連結: http://arc080.contest.atcoder.jp/tasks/arc080_c 作法滿greedy的,每次都挑字典序最小的一組pair,而且中間必定要隔偶數個數字,挑出來後就可以直接寫在前面,因為這方法其實就等價於最後再挑這兩個(因為中間隔偶數個,所以中間一定可以拿完),不過稍微再想兩下就會發現其實還可以知道每次要挑的位置是先奇數再偶數,而且每次後面那個偶數位的一定不能超過前面拔掉的位置之一,稍微處理一下這個細節大概就可以做了。 #include <bits/stdc++.h> using namespace std; typedef pair<int,int> PII; #define FF first #define SS second const int N = 200000 + 5; const int INF = 1<<30; class SegTree{ private: int size; PII nodes[4*N]; inline int lc(int x){return 2*x+1;} inline int rc(int x){return 2*x+2;} void build(int l, int r ,int id, PII arr[]){ if(r-l==1){ nodes[id]=arr[l]; return; } int mid=(l+r)>>1; build(l, mid, lc(id), arr); build(mid, r, rc(id), arr); nodes[id] = min(nodes[lc(id)], nodes[rc(id)]); } ...

[TIOJ] 1316. 晶片設計

題目連結: http://tioj.infor.org/problems/1316 一開始想錯方向,以為是有一堆開頭跟結尾,然後要選一些之類的...。卡了超久做不出來後,才發現其實它就是一堆線段,然後要選一堆線段,使得同一個位置只能最多被覆蓋到兩次。那其實就直接greedy選右界最靠近左邊的就好了(因為越短,表示你越可以選到後面的)。 這裡寫了個線段樹,來判斷可不可以插入,不過應該可以不用,然後也可以直接$O(N)$的做這個操作XD #include <bits/stdc++.h> using namespace std; typedef pair<int,int> PII; #define FF first #define SS second const int N = 4000 + 5; class SegTre{ private: struct node{ int flag=0, val=0; } nodes[N<<3]; int size; inline int lc(int x){return 2*x+1;} inline int rc(int x){return 2*x+2;} inline void push(int l, int r, int id){ if(r-l > 1){ nodes[lc(id)].flag+=nodes[id].flag; nodes[rc(id)].flag+=nodes[id].flag; } nodes[id].val += nodes[id].flag; nodes[id].flag=0; } inline void pull(int id){ ...

[TIOJ] 1679. 抽紙牌(poker)

題目連結: http://tioj.infor.org/problems/1679 把東東吃進來後排序並取到要取的位置即可,而且C++中有個東西叫做pair,他比大小自動就是先比第一項再比第二項 #include <bits/stdc++.h> using namespace std; typedef pair<int,char> PCI; #define N 52 PCI cards[N]; int main(){ ios_base::sync_with_stdio(0);cin.tie(0); int n;cin>>n; for(int i=0;i<n;i++) cin>>cards[i].second>>cards[i].first; sort(cards,cards+n,[](PCI a, PCI b){return a>b;}); int m;cin>>m; cout<<cards[m-1].second<<' '<<cards[m-1].first; return 0; }

[TIOJ] 1853 . Ch1-1.一切的開始

題目連結: http://tioj.infor.org/problems/1853 本題似乎有DP作法,狀態可能是什麼,強制把前i個都轉成0跟1的方法數。不過,其實也可以greedy做,準則是:從右邊面看到左邊時,若看到一個0,則看其左邊是否也為0,若是的話就用翻的,反之則用點的。因為對於一個XXXXX101111的序列,若在0那點用翻的,則會將下一個一變成0,那必定之後又要用翻的,最後會變成XXXXX111111,花費兩個操作,但是用點的只要一個操作;另外一方面,對於一個XXXXX001111,如果都用點的,需花費兩操作,但是如果用翻的最多也是兩操作,並不會比較慘。 #include <bits/stdc++.h> using namespace std; #define N 10000000 char arr[N+5]; int main(){ ios_base::sync_with_stdio(0);cin.tie(0); int n;cin>>n>>arr; int ans=0; bool cur=0; for(int i=n-1;i>=1;i--){ if(arr[i]=='0'+cur){ ans++; if(arr[i]==arr[i-1]) cur=!cur; } } if(arr[0]=='0'+cur) ans++; cout<<ans<<'\n'; return 0; }

[POI] II. Trees

題目連結: http://main.edu.pl/en/archive/oi/2/drz 不難發現其實就照著他說的插入就好,不過因為我寫不太出來該如何判斷往哪邊插跟何時要開點,所以我用另外一種寫法。每次都把最大且相鄰的兩個點合併起來成為一個新節點,而當有人找不到人配時就是不合法的。構造完完整的樹後就直接DFS輸出他要的結果即可。 #include <bits/stdc++.h> using namespace std; #define N 2500 struct lNode{ lNode *l, *r; int next, dep; lNode(){l=NULL;r=NULL;} }; lNode *List[N+5], __nn[4*N]; lNode* nNode(){ static int __cnt=0; return &__nn[__cnt++]; } void dfs2(lNode*); int id=0; void dfs1(lNode*); int main(){ ios_base::sync_with_stdio(0);cin.tie(0); int n;cin>>n; for(int i=0;i<n;i++)List[i]=nNode(); for(int i=0;i<n;i++)List[i]->next=i+1; List[n-1]->next=n-1; int mx=-1; for(int i=0;i<n;i++){ cin>>(List[i]->dep); mx=max(mx, List[i]->dep); } bool flag=1; for(int i=mx;i>=1;i--){ if(!flag)break; ...