【Foreign】减法 [二分][贪心]
减法
Time Limit: 10 Sec Memory Limit: 256 MBDescription
给你一个n个数的序列A,并且给出m次操作B。
操作的含义是:每次从A中选出不同的B_i个数,把它们减去1。一个数>0被看作是可以选择的。
问最多执行完第几个操作。
Input
第一行给出两个整数,序列长度n与操作数m。
第二行n个数,表示序列A。
第三行m个数,表示操作序列B。
Output
输出一个整数,表示最多执行完第几个操作。
Sample Input
3 4
2 1 3
3 1 2 1
Sample Output
3
HINT
1 <= n, m <=100000
Solution
考虑贪心,显然是每次减去大的数。
我们二分答案做到第几次,然后构造一组不上升序列times,表示每个数最多可以被减几次,每次把区间[1, B[i]]加一。
如果每个数都足够大的话,这显然是一组合法解。但是每个数不一定都够减。
我们可以基于这个序列来做调整,考虑什么情况下是合法的呢?
显然,前面不够减的可以是分摊到后面去,而若A从大到小排序,贪心尽量多减,那么后面减的次数又不可能比前面多,所以这样就保证了合法性。
所以先把A从大到小排序然后O(n)递推。
每次 left += times[i] - A[i]。
如果left<=0,表示这个位置的值减完还有剩余,但是这个left不能分摊给后面用,所以我们把left设为0;
如果left>0,可以把left留在后面减,传递下去即可。
递推完只要判断一下最后是否left<=0。
Code
1 #include<iostream> 2 #include<string> 3 #include<algorithm> 4 #include<cstdio> 5 #include<cstring> 6 #include<cstdlib> 7 #include<cmath> 8 using namespace std; 9 typedef long long s64; 10 11 const int ONE = 1000005; 12 const int MOD = 1e9 + 7; 13 14 int n, m; 15 int A[ONE], B[ONE]; 16 int times[ONE]; 17 bool cmp(int a, int b) {return a > b;} 18 19 int get() 20 { 21 int res=1,Q=1; char c; 22 while( (c=getchar())<48 || c>57) 23 if(c=='-')Q=-1; 24 if(Q) res=c-48; 25 while((c=getchar())>=48 && c<=57) 26 res=res*10+c-48; 27 return res*Q; 28 } 29 30 int Check(int mid) 31 { 32 for(int i = 1; i <= n; i++) times[i] = 0; 33 for(int i = 1; i <= mid; i++) 34 { 35 times[1]++, times[B[i] + 1]--; 36 if(B[i] > n) return 0; 37 } 38 39 int left = 0; 40 for(int i = 1; i <= n; i++) 41 { 42 times[i] += times[i - 1]; 43 left += times[i] - A[i]; 44 if(left <= 0) left = 0; 45 } 46 47 return left <= 0; 48 } 49 50 int main() 51 { 52 n = get(); m = get(); 53 for(int i = 1; i <= n; i++) A[i] = get(); 54 for(int i = 1; i <= m; i++) B[i] = get(); 55 sort(A + 1, A + n + 1, cmp); 56 57 int l = 0, r = m; 58 while(l < r - 1) 59 { 60 int mid = l + r >> 1; 61 if(Check(mid)) l = mid; 62 else r = mid; 63 } 64 65 if(Check(r)) printf("%d", r); 66 else printf("%d", l); 67 }