差分+二分答案--P1083 借教室

*前置知识:

  1. 前缀和:$sum_i$表示$0-i$内的所有数的和,求[i-j]内所有数的和就可以用$sum_j-sum_{i-1}$
    1 for(int i=1;i<=n;i++)
    2 {cin>>a[i];sum[i]=sum[i-1]+a[i];}
    3 for(int i=1;i<=q;i++)
    4 {cin>>l>>r;cout<<sum[r]-sum[l-1]<<" ";}
  2. 差分数组:其实是前缀和的逆运算,由差分数组可以求出原数的大小
    1 for(int i=1;i<=n;i++)
    2 {cin>>diff[i];a[i]=diff[i]+a[i-1];}
    3 for(int i=1;i<=n;i++)
    4 {cout<<a[i];}
  3. 关于二分:一般来说,二分是个很有用的优化途径,因为这样会直接导致减半运算,而对于能否二分,有一个界定标准:状态的决策过程或者序列是否满足单调性或者可以局部舍弃性 而在这个题里,因为如果前一份订单都不满足,那么之后的所有订单都不用继续考虑;而如果后一份订单都满足,那么之前的所有订单一定都可以满足,符合局部舍弃性,所以可以二分订单数量。

*代码实现:

  1. 求差分数组,因为是再一段区间加上一个数,所以区间左端点$l$会比$l-1$又多出$d$,而右端点$r$会比$r+1$少多出$d$1 for (int i = 1;i <= x;i++){ 2 dis[l[i]]+=d[i]; dis[r[i]+1]-=d[i]; 3 } 
  2. 求原数组(即需要的教室个数),如果大于我已有的,那么订单不合法
    1 for (int i = 1;i <= n;i++){
    2     need[i]=need[i-1]+dis[i];
    3     if (need[i]>room[i]) return false;
    4   }
  3. 二分求第一个不符合答案的订单编号:如果当前份订单不满足,那么后面的一定都不满足,前面的不一定满不满足,找左区间,如果当前订单满足,那么前面的一定都满足,后面的不一定满不满足,找右区间
    1 while (ll<rr){
    2     int mid=(ll+rr)>>1;
    3     if (solve(mid)) ll=mid+1;
    4     else rr=mid;
    5   }

完整代码:

 1 #include <iostream>
 2 #include <algorithm>
 3 #include <cstdio>
 4 #include <cstring>
 5 using namespace std;
 6 const int  maxn=1e6+10;
 7 int n,m;
 8 int room[maxn],l[maxn],r[maxn],d[maxn];
 9 int dis[maxn],need[maxn];
10 bool solve(int x){
11   memset(dis,0,sizeof(dis));
12   for (int i = 1;i <= x;i++){
13     dis[l[i]]+=d[i]; dis[r[i]+1]-=d[i];
14   }
15   for (int i = 1;i <= n;i++){
16     need[i]=need[i-1]+dis[i];
17     if (need[i]>room[i]) return false;
18   }
19   return true;
20 }
21 int main(){
22   scanf ("%d%d",&n,&m);
23   for (int i = 1;i <= n;i++) scanf ("%d",&room[i]);
24   for (int  i = 1;i <= m;i++) scanf ("%d%d%d",&d[i],&l[i],&r[i]);
25   if (solve(m)) {printf("0\n");return 0;}
26   int ll = 1,rr=m;
27   while (ll<rr){
28     int mid=(ll+rr)>>1;
29     if (solve(mid)) ll=mid+1;
30     else rr=mid;
31   }
32   printf("-1\n%d\n",ll);
33   return 0;
34 }

 

posted @ 2020-10-10 11:38  小又又  阅读(205)  评论(1编辑  收藏  举报