JOI 10 本選 Bug Party ( Binary Search + MLE Persistent Segment Tree / AC Priority Queue )
Bug Party | Aizu Online Judge
很容易想到,如果固定最小值b,那麼我們必須由小到大拿進所有a,直到 sum( a ) ≤ b * cnt 不再成立。由於要快速( O( lg n ) 以下 ) 求出大於等於某個b值的最小的x個a值的總和,條件形式複雜,如果不加功夫用持久化線段樹亂搞,雖然時間複雜度是對的,但會MLE。
// RE and MLE #include <bits/stdc++.h> using namespace std; const int MAXN = 3e5 + 5; const int MAXA = 1e5 + 5; const int MAXB = 1e5 + 5; const int MAXMEM = 1.5e5; // 実は少なくとも 3e6は欲しい typedef long long ll; struct itvt{ static itvt mem[ MAXMEM ]; int lb, rb; int cnt; ll sum; itvt *lc, *rc; itvt(): cnt(0), sum(0LL), lc(NULL), rc(NULL){} void pull(){ cnt = lc->cnt + rc->cnt; sum = lc->sum + rc->sum; } } itvt::mem[ MAXMEM ], *ptr = itvt::mem, *root[ MAXN ]; itvt* build(int lb, int rb){ itvt *t = new( ptr++ ) itvt(); t->lb = lb, t->rb = rb; if( lb == rb ) return t; int mid = ( lb + rb ) / 2; t->lc = build( lb, mid ); t->rc = build( mid + 1, rb ); return t; } itvt* clone(itvt *t){ itvt *res = new( ptr++ )itvt(); res->lb = t->lb, res->rb = t->rb; res->cnt = t->cnt, res->sum = t->sum; res->lc = t->lc, res->rc = t->rc; return res; } itvt *add(itvt *t, int x, int v){ itvt *res = clone( t ); if( t->lb == t->rb ){ ++res->cnt; res->sum += v; return res; } int mid = ( t->lb + t->rb ) / 2; if( x <= mid ) res->lc = add( t->lc, x, v ); else res->rc = add( t->rc, x, v ); res->pull(); return res; } typedef pair<ll, int> pli; // sum / count pli qsum(itvt *t, int ql, int qr){ if( ql <= t->lb && t->rb <= qr ) return pli( t->sum, t->cnt ); int mid = ( t->lb + t->rb ) / 2; pli res = pli( 0LL, 0 ); if( ql <= mid ){ pli g = qsum( t->lc, ql, qr ); res.first += g.first, res.second += g.second; } if( mid < qr ){ pli g = qsum( t->rc, ql, qr ); res.first += g.first, res.second += g.second; } return res; } int n; struct Ant{ int a, b; Ant(int _a = 0, int _b = 0): a( _a ), b( _b ){} } ant[ MAXN ], a_sorted_ant[ MAXN ]; bool cmpa(const Ant &x, const Ant &y){ return x.a < y.a; } bool cmpb(const Ant &x, const Ant &y){ return x.b < y.b; } void solve(){ int ans = 0; for(int i = 0; i < n; ++i) a_sorted_ant[ i ] = ant[ i ]; sort( a_sorted_ant, a_sorted_ant + n, cmpa ); root[ 0 ] = build( 1, 100000 ); // 1 <= b <= 100000 for(int i = 1; i <= n; ++i) root[ i ] = add( root[ i - 1 ], a_sorted_ant[ i - 1 ].b, a_sorted_ant[ i - 1 ].a ); sort( ant, ant + n, cmpb ); for(int i = 0; i < n; ++i){ int b = ant[ i ].b; int lb = 0, rb = n; // finally will take how much int t = -1; while( lb <= rb ){ int mid = ( lb + rb ) / 2; pli g = qsum( root[ mid ], b, 100000 ); ll asum = g.first; int cnt = g.second; if( asum <= (ll)cnt * b ) t = cnt, lb = mid + 1; else rb = mid - 1; } ans = max<int>( ans, t ); } printf("%d\n", ans); } int main(){ // printf("%d\n", (int)(sizeof(itvt))); = 40 scanf("%d", &n); for(int i = 0; i < n; ++i) scanf("%d%d", &ant[ i ].a, &ant[ i ].b); solve(); return 0; }
該注意到的是,如果從許可量大的開始取,那麼我們只需要在意現在準備取的那個元素的b,具體來說,假設要判斷取x個元素是否可行,我們可以先取排放量最大的x - 1個元素,接著在看這第x個的b來判斷是否取進之後是可行的,若不可行,就維護這x - 1個元素使得一路排放量最小。到這裡思路就很明顯了,二分搜這個x,利用priority queue來維護和判斷是否可行。O( n lg ^ 2 n )。
結論:雙重有序的題目如果無法有效的處理,除了要想想看雙向佇列和爬行,有時候是可以透過改變遍歷元素的方法,使得某一維度隱約地出現在遍歷的順序中,或許能因為排序過省略某些檢查,進而達到降低複雜度的效果。
#include <bits/stdc++.h> using namespace std; const int MAXN = 3e5 + 5; const int MAXA = 1e5 + 5; const int MAXB = 1e5 + 5; typedef long long ll; int n; struct Ant{ int a, b; Ant(int _a = 0, int _b = 0): a(_a), b(_b){} } ant[ MAXN ]; bool cmpa(const Ant &x, const Ant &y){ return x.b > y.b; // b: limit of take in } bool ok(int x){ ll asum = 0LL; priority_queue<int> foo; for(int i = 0; i < x - 1; ++i) asum += ant[ i ].a, foo.push( ant[ i ].a ); for(int i = x - 1; i < n; ++i){ if( ( asum + ant[ i ].a ) <= (ll)x * ant[ i ].b ) return true; if( foo.empty() ) continue; // OOPS, when x == 1 if( ant[ i ].a > foo.top() ) continue; asum -= foo.top() - ant[ i ].a; foo.pop(); foo.push( ant[ i ].a ); } return false; } void solve(){ sort( ant, ant + n, cmpa ); int lb = 0, ub = n; int ans = -1; while( lb <= ub ){ int mid = ( lb + ub ) / 2; if( ok( mid ) ) ans = mid, lb = mid + 1; else ub = mid - 1; } printf("%d\n", ans); } int main(){ scanf("%d", &n); for(int i = 0; i < n; ++i) scanf("%d%d", &ant[ i ].a, &ant[ i ].b); solve(); return 0; }