1. 程式人生 > >BZOJ5321 JXOI2017加法(二分答案+貪心+堆+樹狀陣列)

BZOJ5321 JXOI2017加法(二分答案+貪心+堆+樹狀陣列)

  二分答案後得到每個位置需要被加的次數。考慮貪心。從左到右考慮每個位置,將以該位置為左端點的區間按右端點從大到小加進堆。看該位置還需要被加多少次,如果不需要加了就不管,否則取堆頂區間將其選擇,BIT實現區間覆蓋。

#include<iostream> 
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using
namespace std; #define ll long long #define N 200010 char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;} int gcd(int n,int m){return m==0?n:gcd(m,n%m);} int read() { int x=0,f=1;char c=getchar(); while (c<'
0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int T,n,m,k,a[N],c[N],tree[N]; priority_queue<int> q; vector<int> b[N]; void add(int k,int x){while (k<=n) tree[k]+=x,k+=k&-k;} int
query(int k){int s=0;while (k) s+=tree[k],k-=k&-k;return s;} bool check() { for (int i=1;i<=n;i++) tree[i]=0;int cnt=0; while (!q.empty()) q.pop(); for (int i=1;i<=n;i++) add(i,c[i]-c[i-1]); for (int i=1;i<=n;i++) { for (int j=0;j<b[i].size();j++) q.push(b[i][j]); int x=query(i); for (;x>0;x--) { if (q.empty()) return 0; int y=q.top();q.pop(); if (y<i) return 0; add(i,-1),add(y+1,1); if ((++cnt)>k) return 0; } } return 1; } int main() { #ifndef ONLINE_JUDGE freopen("bzoj5321.in","r",stdin); freopen("bzoj5321.out","w",stdout); const char LL[]="%I64d\n"; #else const char LL[]="%lld\n"; #endif T=read(); while (T--) { n=read(),m=read(),k=read();int p=read(); int l=100000000,r,ans; for (int i=1;i<=n;i++) l=min(l,a[i]=read()),b[i].clear(); for (int i=1;i<=m;i++) { int x=read(),y=read(); b[x].push_back(y); } r=l+m*p; while (l<=r) { int mid=l+r>>1; for (int i=1;i<=n;i++) c[i]=mid<=a[i]?0:(mid-a[i]-1)/p+1; if (check()) l=mid+1,ans=mid; else r=mid-1; } printf("%d\n",ans); } return 0; }