报名了acwing的蓝桥杯每日一题,在此总结今晚课上的内容,以便日后复习。
今晚的例题是借教室问题,题目如下:
题目虽然非常的长,但是仔细阅读我们就会发现他其实并不难。接下来我们做简单的分析:
-
首先我们可以发现他给的数据非常大,如果我们用暴力做法一定是拿不到满分的,此时我们就要考虑使用算法来简便操作。
我们假设k为第一次无法满足条件的情况,把线段从中点分开,我们可以得出他有二段性这里我们补充一下什么是二段性:我们可以找到一个性质,使左边满足右边不满足,亦或者右边满足左边不满足。这样通过数次的循环,就可以分出答案的位置
int l= 0,r=m;
while(l<r)
{
int mid = l+r+1>>1;//二分出来的中点
//这里的‘>>1’表示除以2,相比‘/’,使用‘>>’更加快速
if(check(mid)) l =mid;//满足条件,更新左边界
else r =mid-1;//不满足条件,更新右边界
}
以上步骤为二分,值得注意的是,二分时需要注意边界问题,不然会造成time limit error,这里教大家一个简单的方法,即当if后是l=mid时,就让l+r再加上1;反之如果if后是r = mid,就值需要让mid= l+r>>1即可。
我们可以通过分析得知,这道题也是在一个数列的区间内加上或减去一个数,
根据我上一篇博客的例题不难发现,这里考察的是差分(关于差分的内容我就不再赘述,想详细了解的可以点击这里查看详细)
我们接下来便重点了解check是如何实现的:
const int N = 100010;
typedef long long ll;
ll b[N];//差分数组
int n,m;//n表示天数,m表示订单的数量
int w[N],d[N],s[N],t[N];//w表示剩余教室的个数,d表示的是需要租借的个数
//s表示开始接的天数,t表示结束租借的天数
bool check(int mid)
{
memset(b,0,sizeof b);//将b的内容全部初始化为0
for(int i =1;i<=mid;i++)
{
b[s[i]] += d[i];
b[t[i]+1] -=d[i];//将s[i]到t[i]之间的值都加上d[i];
}
for(int i =1;i<=n;i++)
{
d[i]+=d[i-1];
if(b[i]>w[i]) return false;
}
return true;
}
到此为止,这道题基本就已经做完了,接下来的就是补全步骤。
完整代码如下:
#include <iostream>
#include <cstring>
using namespace std;
typedef long long ll;
const int N = 1000010;
int n,m;//n表示天数,m表示订单的数量
int w[N],d[N],s[N],t[N];//w表示剩余教室的个数,d表示的是需要租借的个数
//s表示开始接的天数,t表示结束租借的天数
ll b[N];//差分数组
bool check(int mid)
{
memset(b,0,sizeof b);//将差分数组初始化
for(int i =1;i<=mid;i++)
{
b[s[i]]+=d[i];
b[t[i]+1]-=d[i];
}
for(int i =1;i<=n;i++)
{
b[i]+=b[i-1];
if(b[i]>w[i]) return false;
}
return true;
}
int main()
{
cin>>n>>m;
for(int i = 1;i<=n;i++) scanf("%d",&w[i]);
for(int i =1;i<=m;i++) scanf("%d%d%d",&d[i],&s[i],&t[i]);
int l=0,r=m;
while(l<r)
{
int mid =l+r+1>>1;
if(check(mid)) l = mid;
else r=mid-1;
}
if(r==m) puts("0");
else printf("-1\n%d\n",r+1);
return 0;
}
有几点需要注意
- 当数据非常大的时候,尽量使用输入输出时尽量使用scanf和printf,比cin和cout快很多
- 我可以看到w和d的范围是非常大的,已经超出了int,所以我们这里就要用long long,我这里是为了方便,直接把long long设成ll
- 我在前缀和与差分里说过,使用这两个方法时要从1开始,但我这里为什么从0开始了呢,因为我们考虑到了一种非常极端的情况,那就是第一个订单的供应量就无法满足。
- memset的作用是快速初始化数组,头文件是
以上就是全部内容了,如果有什么不对的地方或者改进的方法还请告诉我,我都会听取建议。