首页 > 其他分享 >[题解]P1083 [NOIP2012 提高组] 借教室

[题解]P1083 [NOIP2012 提高组] 借教室

时间:2024-07-04 22:30:22浏览次数:22  
标签:NOIP2012 int 题解 mid long cf P1083 合法 define

[题解]P1083 [NOIP2012 提高组] 借教室

解法\(1\):线段树 - \(O((n+m)\log n)\)

比较直观的一种做法,但是可能需要卡一下输入(这里没卡也过了,但要注意输入是\(10^6\)级的,为了保险一定要加)。

#include<bits/stdc++.h>
#define lc (x<<1)
#define rc ((x<<1)|1)
#define int long long
using namespace std;
int n,m,a[1000010],minn[4000010],tag[4000010];
void update(int x){
	minn[x]=min(minn[lc],minn[rc]);
}
void ch(int x,int v){
	tag[x]+=v;
	minn[x]+=v;
}
void pushdown(int x){
	if(tag[x]){
		ch(lc,tag[x]);
		ch(rc,tag[x]);
		tag[x]=0;
	}
}
void build(int l,int r,int x){
	if(l==r){
		minn[x]=a[l];
		return;
	}
	int mid=(l+r)>>1; 
	build(l,mid,lc);
	build(mid+1,r,rc);
	update(x);
}
void modify(int a,int b,int v,int l,int r,int x){
	pushdown(x);
	if(a<=l&&r<=b){
		ch(x,v);
		return;
	}
	int mid=(l+r)>>1;
	if(a<=mid) modify(a,b,v,l,mid,lc);
	if(b>mid) modify(a,b,v,mid+1,r,rc);
	update(x);
}
int query(int a,int b,int l,int r,int x){
	pushdown(x);
	if(a<=l&&r<=b) return minn[x];
	int mid=(l+r)>>1,ans=LLONG_MAX;
	if(a<=mid) ans=min(ans,query(a,b,l,mid,lc));
	if(b>mid) ans=min(ans,query(a,b,mid+1,r,rc));
	return ans;
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	build(1,n,1);
	for(int i=1;i<=m;i++){
		int v,l,r;
		cin>>v>>l>>r;
		modify(l,r,-v,1,n,1);
		if(query(1,n,1,n,1)<0){
			cout<<"-1\n"<<i<<"\n";
			return 0;
		}
	}
	cout<<"0\n";
	return 0;
}

解法\(2\):二分答案 - \(O((n+m)\log m)\)

二分枚举申请的编号,对于枚举出的编号。通过差分,计算出处理完当前编号后,每一天剩余的教室数量。如果最终结果存在负数,则r=mid,否则l=mid+1。因为我们要找的是使存在负数的最小编号。

#include<bits/stdc++.h>
#define int long long
#define N 1000010
#define M 1000010 
using namespace std;
int n,m,a[N],b[N],tb[N],c[M],ld[M],rd[M];
bool check(int d){
	for(int i=1;i<=n;i++) tb[i]=b[i];
	for(int i=1;i<=d;i++) tb[ld[i]]-=c[i],tb[rd[i]+1]+=c[i];
	for(int i=1;i<=n;i++) tb[i]+=tb[i-1];
	for(int i=1;i<=n;i++) if(tb[i]<0) return 1;
	return 0;
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		b[i]=a[i]-a[i-1];
	}
	for(int i=1;i<=m;i++) cin>>c[i]>>ld[i]>>rd[i];
	int l=1,r=m;
	bool flag=0;
	while(l<r){
		int mid=(l+r)>>1;
		if(check(mid)){//不合法 
			flag=1;
			r=mid;
		}else{
			l=mid+1;
		}
	}
	if(!flag) cout<<"0\n";
	else cout<<"-1\n"<<l<<"\n";
	return 0;
}

解法\(3\) - \(O(n+m)\)

总体思路就是用一个指针\(j\),初始为\(m\)。对于每一天,判断完成操作\(1\sim j\)后是否仍然合法。如果不合法了,就把\(j\)往前挪,同时撤回操作,直到该天合法为止。

最后,如果\(j=m\),则说明所有操作都是合法的,输出0

否则,我们需要输出导致不合法的第一个操作。而\(j\)表示的是合法的最后一个操作,输出\(j+1\)即可。

此算法的优势在于,枚举的每一天不需要从最后开始移动指针,而是从上一天的位置开始移动,大大减少了冗余操作。

注意到\(j\)是只减不增的,所以while循环一共最多执行\(m\)次。而for一共执行\(n\)次,所以复杂度是\(O(n+m)\)。

#include<bits/stdc++.h>
#define N 1000010
#define M 1000010
#define int long long
using namespace std;
int n,m,a[N],c[M],l[M],r[M];
int cf[N];
signed main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=m;i++){
		cin>>c[i]>>l[i]>>r[i];
		cf[l[i]]+=c[i],cf[r[i]+1]-=c[i];
	}
	int j=m,sum=0;
	//sum表示完成操作1~j后,第i天一共用去多少教室
	for(int i=1;i<=n;i++){
		sum+=cf[i];//cf[i]表示完成操作j之后,第i天一共用去多少个教室
		while(sum>a[i]){//用去的>已有的,不合法,需要撤回操作
			cf[l[j]]-=c[j],cf[r[j]+1]+=c[j];
			if(l[j]<=i&&i<=r[j]) sum-=c[j];
			j--;
		}
	}
	if(j==m) cout<<"0\n";
	else cout<<"-1\n"<<j+1<<"\n";
	return 0;
}

标签:NOIP2012,int,题解,mid,long,cf,P1083,合法,define
From: https://www.cnblogs.com/Sinktank/p/18283918

相关文章

  • SP15620 POSTERIN - Postering 题解
    题目传送门前置知识单调栈解法容易有每个建筑物的宽度对答案没有影响,故可以将其宽度均看作\(1\)。在最优策略下,对于每张海报,其高度一定等于所覆盖的楼的最小高度。单调栈维护最小高度,记录额外海报数量(与先前高度相等时可以少用一张海报)。最终,用总张数\(n\)减去额外海报......
  • reverse 题解
    reverse题解注意到本题数据范围较大且与数位有关,考虑数位DP。我们发现对于每个询问,我们可以将第一个条件拆开之后差分,可以先从后往前DP,预处理出末尾满足$L\le\operatorname{reverse}(n)\leR$的个数,之后使用试填法填数即可。具体地,在预处理时,处理出顶到上界,顶到下界......
  • Kithara常见问题解答
    目录通用问题我的内核驱动程序已经签名了吗?是否可以在打开驱动程序时防止显示介绍窗口?Windows7仍然支持吗?错误0x10142422(`KSERROR_CANNOT_START_KERNEL`)在`KS_openDriver`时出现?错误10145241(KSERROR_CANNOT_START_KERNEL)在KS_openDriver时出现?可以在C#应用程......
  • javascript url 传递参数中文乱码问题解决方案
    在JavaScript中,传递URL参数时,如果参数包含中文字符,可能会出现乱码问题。解决这一问题可以使用encodeURIComponent和decodeURIComponent函数。这些函数会对URL参数进行编码和解码,确保特殊字符(包括中文字符)能够被正确传递和解析。以下是一个完整的解决方案示例: 1.......
  • [JLU] 数据结构与算法上机题解思路分享-
    前言首先,请务必自己尽全力尝试实现题目,直接看成品代码,思维就被拘束了,也很容易被查重。这里只是思路解析的博客,代码仓库在JLU_Data_Structures_Record希望你能在这里找到你想要的:)第三次上机A手撕BST分数50作者朱允刚单位吉林大学对一棵初始为空的二叉查找树(Binary......
  • 牛客小白月赛97 A-D题解
    AAAAAAAAAAAAAAAAAAAAA-----------------------------题解-------------------------------------------统计数组中有没有出现三个相同的边即可点击查看代码#include<bits/stdc++.h>usingnamespacestd;intmain(){intn;cin>>n;map<int,int>m;int......
  • P2286 [HNOI2004] 宠物收养场 题解
    P2286[HNOI2004]宠物收养场set做法题链\(_{洛谷}\)\(_{题库}\)思路一眼查找前驱后继的题。注意到一句话:那么用set就没有什么阻碍了,方便又快捷。题意很简单,若宠物多则查找与人需求最接近的上下两个值,人多则找与宠物最接近的上下两个人的值。出题人很善良,把选人和选宠物......
  • 刺杀 题解
    题目简述你在玩一个游戏,需要刺杀\(n\)个敌人。可以肉搏或者用子弹击杀敌人。肉搏第\(i\)个敌人会使你的体力值减少\(x_i\),你要保证你的体力值始终非负。击杀第\(i\)个敌人后,会获得\(y_i\)颗子弹,有可能\(y_i\)为\(0\),这时候你啥都拿不到。你初始体力值为\(s\),有一个......
  • P10589 题解
    经典题。tag:数状数组。开一个权值树状数组,从左往右遍历,统计左边比\(y_i\)小的数字个数\(ul_i\)与比\(a_i\)大的数字个数\(dl_i\);然后从右往左遍历,统计右边比\(y_i\)小的数字个数\(dr_i\)与比\(a_i\)大的数字个数\(ur_i\)。两个答案即为\(\sum_{i=1}^ndl_i\cdo......
  • CSP-S 2005 T1 谁拿了最多奖学金【题解】
    1.题目描述某校的惯例是在每学期的期末考试之后发放奖学金。发放的奖学金共有五种,获取的条件各自不同:院士奖学金,每人 8000 元,期末平均成绩高于 80 分(>80),并且在本学期内发表1篇或1篇以上论文的学生均可获得;五四奖学金,每人 4000 元,期末平均成绩高于 85 分(>85),并且班级......