差分+二分答案--P1083 借教室
*前置知识:
- 前缀和:$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]<<" ";}
- 差分数组:其实是前缀和的逆运算,由差分数组可以求出原数的大小
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];}
- 关于二分:一般来说,二分是个很有用的优化途径,因为这样会直接导致减半运算,而对于能否二分,有一个界定标准:状态的决策过程或者序列是否满足单调性或者可以局部舍弃性。 而在这个题里,因为如果前一份订单都不满足,那么之后的所有订单都不用继续考虑;而如果后一份订单都满足,那么之前的所有订单一定都可以满足,符合局部舍弃性,所以可以二分订单数量。
*代码实现:
- 求差分数组,因为是再一段区间加上一个数,所以区间左端点$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 }
- 求原数组(即需要的教室个数),如果大于我已有的,那么订单不合法
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 }
- 二分求第一个不符合答案的订单编号:如果当前份订单不满足,那么后面的一定都不满足,前面的不一定满不满足,找左区间,如果当前订单满足,那么前面的一定都满足,后面的不一定满不满足,找右区间
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 }