👍天再高又怎样,踮起脚尖就更接近阳光。以后的你会感谢现在每天坚持努力奋斗的自己。👍
写在前面:暑期在校集训7.1~8.18(合计49天),菜的扣脚,在csdn上记录自己的训练过程,努力每天坚持刷题,补题,写了的都会在这里记录,最重要的是坚持!坚持!思考!思考!。加油!
集训1 (13题)
题号 | 标题 |
---|---|
A | 校门外的树 |
B | 值周 |
C | [CQOI2009]中位数图 |
D | [HNOI2003]激光炸弹 |
E | 二分 |
F | 货仓选址 |
G | 牛可乐和魔法封印 |
H | [USACO 2009 Dec S]Music Notes |
I | [NOIP2015]跳石头 |
J | 晾衣服 |
K | 数字组合 |
L | [CQOI2010]扑克牌 |
M | [NOIP2012]借教室 |
A校门外的树(暴力过了)
注意:每个位置都可以重复种树,也就是一个位置可能有很多种树,都是有效的。
转为括号序列,左括号:区间左端点 右括号:区间右端点 包括区间左右端点!!!
求l-r区间内树的种数:用1-r内左括号的数量减1-(l-1)区间中右括号的数量
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=5e4+10;
int a[N],b[N];
int main(){
int n,m;
scanf("%d%d",&n,&m);
while(m--){
int k,l,r;
scanf("%d%d%d",&k,&l,&r);
if(k==1){ //k=1
a[l]++;
b[r]++;
}
else{ //k=2
int res=0;
int x=0,y=0; //x-左括号 y-右括号
for(int i=1;i<=r;i++)
x+=a[i];
for(int i=1;i<=l-1;i++)
y+=b[i];
res=x-y;
printf("%d\n",res);
}
}
return 0;
}
B值周--差分前缀和--easy
前缀和适用于对于某一个区间内的所有数加上同一个值,用于处理区间
ios::sync_with_stdio(false); 即可用cin和cout速度和scanf相当
在main函数里面的数组是开在栈区(stack)的,而在函数外面的是开在数据区的。栈区的内存比较小,所以当数组非常大的时候,就会报错。而把数组放在数据区就不会出现这个问题,因为数据区的内存比较的大
-
栈区:由操作系统自动分配释放,存放函数的参数值,局部变量的值,当不需要式系统会自动清除。
-
堆区:由new分配的内存块,不由编译器管,由应用程序控制(相当于程序员控制)。如果程序员没有释放掉,程序结束后,操作系统会自动回收。
-
数据区:也称全局区或者静态区,存放全局的东西类似全局变量。
-
代码区:存放执行代码的地方,类似if else,while,for这种语句。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> using namespace std; const int N=1e8+10; int a[N]; //只用写差分数组 sum+=a[i] 每次加后值就是差分数组的前缀和相加就是对应区间内的0000112211000情况!看为0即表示还在!res++ int main(){ int l,m; scanf("%d%d",&l,&m); while(m--){ //差分 int x,y; scanf("%d%d",&x,&y); a[x]+=1; //👍 a[y+1]-=1;//👍 } int sum=0,res=0; for(int i=0;i<=l;i++){ //注意这里区间是从0到l!!包括两端! sum+=a[i]; if(sum==0)res++; //只有区间内的那一部分的值是!=0的(也不用考虑是不是他的值为2 3 等),区间后面一部分的值还是0 } printf("%d",res); return 0; }
C中位数图--思维题
中位数最重要的性质:大于中位数的数字个数等于小于中位数的数字个数 ,所以大于还是小于才是最重要的,抽象来抵消!大于就是1 小于就是-1,分为左区间,右区间和两边来处理!所以开num数组记录,因为要使用到b的位置所以还要有一个pos来记录
你的ans最少都是1
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
//从第一个开始看!
const int N=1e5+10;
int a[N],num[N<<1];
int main(){
int n,b;
scanf("%d%d",&n,&b);
int x,pos=0;
for(int i=1;i<=n;i++){
scanf("%d",&x);
if(x<b)a[i]=-1;
else if(x>b)a[i]=1;
else {
a[i]=x;
pos=i;
}
}
int sum=0,res=1;
for(int i=pos-1;i>=1;i--){
sum+=a[i];
num[n+sum]++; //注意这里是加
if(!sum) res++;
}
sum=0; //一定要记得更新!
for(int i=pos+1;i<=n;i++){
sum+=a[i];
if(!sum)res++;
res+=num[n-sum]; //这里才能用减 不用else 因为还有u左右对称都是0情况!
}
printf("%d",res);
return 0;
}
D激光炸弹--二维前缀和
求子矩阵的和S[i] [j]
从1 1 开始就是最左上角的方格就是1 1 ,然后你要求r方框大小的东西,+r之后要减去1,才是正常r范围大小的方框!
x2=i+r-1 y2=j+r-1 x1=i x2=j
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=5010;
int a[N][N],s[N][N];
int main(){
int n,r;
scanf("%d%d",&n,&r);
while(n--){
int x,y,v;
scanf("%d%d%d",&x,&y,&v);
a[x+1][y+1]=v; //从左上角就是为1 1 的点->输入也是从1 1 所以输入都要+1
}
//求前缀和数组
for(int i=1;i<=5001;i++)
for(int j=1;j<=5001;j++){
a[i][j]=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+a[i][j]; //就是四步
}
//输出结果
int ans=-1; //注意这里结果是-1 因为你有个max
for(int i=1;i<=5001;i++) //这里不能用5010 因为开的范围是5010 最多到5009
for(int j=1;j<=5001;j++){
if(i+r-1<=5001&&j+r-1<=5001)ans=max(ans,a[i+r-1][j+r-1]-a[i+r-1][j-1]-a[i-1][j+r-1]+a[i-1][j-1]); //求子矩阵的和也是四步 x2和y2不变
}
cout<<ans;
return 0;
}
E二分(差分+离散化)
#include <iostream>
#include <map>
using namespace std;
map<int,int>a;
int inf=0x3f3f3f3f;
int main()
{
int n;
cin>>n;
for(int i=0; i<n; i++)
{
int x;
char op;
cin>>x>>op;
if(op=='.')a[x]++,a[x+1]--;
if(op=='+')a[x]--,a[-inf]++;
if(op=='-')a[x+1]++;
}
int temp=0,ans=0;
for(auto it:a)//遍历map
{
temp+=it.second;//加上每个map对应的值
ans=max(temp,ans);
}
cout<<ans<<endl;
return 0;
}
F货舱选址--思维题
无穷大:const int INF=0x3f3f3f3f;
无论仓库建在哪里,仓库到第i个坐标和到第n-i-1个坐标的距离是相等的,所以只需要两两配对相减即可实现。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1e5+10;
int a[N];
int main(){
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
sort(a,a+n);
int i=0,j=n-1,res=0;
while(i<j){
//这里不能用i!=j 因为与可能i++和j--同时则直接错过了
res+=abs(a[j]-a[i]);
i++;j--;
}
printf("%d",res);
return 0;
}
G牛可乐和魔法封印--典型二分题
二分左右端点和一般使用情况到底如何
大于等于+记得+1
找第一个大于等于x的位置(左边界):
找最后一个小于等于x的位置(有边界):q[mid]<=x l=mid r=mid-1 mid=(l+r+1)>>2
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=1e5+10;
int a[N];
int n;
int main(){
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
sort(a,a+n);
int q;
scanf("%d",&q);
while(q--){
int x,y;
int res=0;
scanf("%d%d",&x,&y);
int l=0,r=n-1,ll=0,rr=0;
while(l<r){
int mid=(l+r)/2;
if(a[mid]>=x)r=mid;//这里为什么是要为>=??
else l=mid+1;
}
ll=l;
l=0,r=n-1;
while(l<r){
int mid=(l+r+1)/2;
//每次都要二分,所以mid写在循环里面
//因为你每次要找的都是大于等于的所以
if(a[mid]<=y)l=mid;
else r=mid-1;
}
rr=l;
res=rr-ll+1;
if(a[0]>y||x>a[n-1])res=0; //特殊情况要特别判断!!!
printf("%d\n",res);
}
return 0;
}
H Music Notes(英文题面--二分查找)
维护前缀和,对于每次询问找到第一个大于它的数--也就是upper_bound的下标,就是答案
二分查找:
①从小到大(则找大)
lower_bound(begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于等于num的数字,找到返回该数字的地址,不存在则返回end,通过返回的地址减去起始地址begin,得到找到数字在数组中的下标
upper_bound(begin,end,num):第一个大于num的数字
②从大到小:(则找小)
lower_bound:第一个小于等于的
upper_bound:第一个小于的
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
//从0开始唱歌 前缀和:即a[i]=第1个大于唱完第i个音符的位置的下标(从0开始) 输入查询时间x,upperbound也是找到第一个大于x的数的地址,再减去起始地址a=a[i]在数组中的下标,即i,即为结果
const int N=5e4+10;
int a[N];
int main(){
int n,q;
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++){//你是从1开始存的
int res=0;
scanf("%d",&a[i]);
a[i]+=a[i-1];
}
while(q--){
int x;
scanf("%d",&x);
printf("%d\n",upper_bound(a+1,a+1+n,x)-a);//减去起始地址就是他的下标值大小
//所以求应该是从a+1到a+1+n-1判断,正好upper_bound是到end-1
}
return 0;
}
I跳石头(二分答案)
最小值最大|最大值最小|求最大值|求最小值->考虑二分
按照答案二分 判断条件是所有小于跳跃距离的数量
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=5e4+10;
int s[N];
int l,n,m;
bool check(int k){ //判断合法性
int ans=0;
for(int i=1,j=0;i<=n+1;i++){ //最后一个的距离也算!!!
if(s[i]-s[j]<=k)ans++;
else j=i;
}
return ans<=m; //如果等于m说明也可能有一个大于的距离
}
int main(){
scanf("%d%d%d",&l,&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&s[i]);
s[n+1]=l; //最后一个距离也要算上!!!
//二分 mid小了
int l=0,r=l;
while(l<r){
int mid=(l+r+1)/2;
if(check(mid))l=mid;
else r=mid-1;
}
printf("%d",l+1);
}
J晾衣服(二分答案--和跳石头类似)
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
//求烘干的最短时间
const int N=1e5+10;
typedef long long ll;
int a[N];
int n,k;
bool check(int x){
ll sum=0;
for(int i=1;i<=n;i++){
if(a[i]>x)
sum+=(a[i]-x+k-1)/k;
}
return sum<=x;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
cin>>k;
sort(a+1,a+1+n);
int l=0,r=1e9;
if(k==1){
cout<<a[n];
return 0;
}
k--;
while(l<r){
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<l;
return 0;
}
数字组合(二分)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<unordered_map>
using namespace std;
//四个数字和为0则为一种合法的组合方案
typedef long long ll;
const int N=1e3+10;
int a[N],b[N],c[N],d[N];
const int M=1e6+10;
int p[M],q[M];
unordered_map<int, int>mp;
int n;
int main(){
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i]>>b[i]>>c[i]>>d[i];
//既然是四个人,四重循环必然爆 换成两重循环做
//用map存进去
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
mp[a[i]+b[j]]++;
}
}
//计算
ll sum=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
if(mp.find(-c[i]-d[j])!=mp.end())
sum+=mp[-c[i]-d[j]];
}
cout<<sum;
return 0;
}
L扑克牌--二分答案(组成最多套牌的牌的套数)
二分答案的范围:0~(1e9+10)
最后答案为l-1
重点:判断jocker用了几张(大于mid和大于m都错)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=55;
int a[N];
int n,m;
typedef long long ll;
//每一次放卡牌只能接着上一个数的末尾放joker 所以最多放置mid张joker
// 放jocker数大于mid错!
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
int l=0,r=1e9;
while(l<r){
int mid=(l+r+1)>>1;
ll sum=0;
for(int i=1;i<=n;i++){
if(a[i]<mid)
sum+=mid-a[i];
}
if(sum>mid||sum>m)r=mid-1;
else l=mid;
}
cout<<l-1<<endl;
return 0;
}
M借教室--二分+差分
减法的前缀和:本天比前一天少的天数得r数组,用差分数组处理每天情况,处理完后从前往后加起来,判断是否出现负数,出现了就为有误即停止计算。
前缀和:a数组是b数组得前缀和 b数组就是a数组的差分
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
//借阅教室 只要不能分配立即停止输出修改订单
int n,m;
const int N=1e6+10;
typedef long long ll;
int r[N],x[N],a[N],b[N],c[N],e[N];
//r为原数组 x为差分数组
//a借多少天 b和c是从第几天借到第几天
bool check(int k){
//错了返回true
for(int i=1;i<=n;i++)
e[i]=x[i];
for(int i=1;i<=k;i++){
e[b[i]]-=a[i];
e[c[i]+1]+=a[i];
}
int res=0;
for(int i=1;i<=n;i++){
res+=e[i];
if(res<0)return true;
}
return false;
}
int main(){
scanf("%d%d",&n,&m);
//求差分数组
for(int i=1;i<=n;i++)
{
scanf("%d",&r[i]);
x[i]+=r[i];
x[i+1]-=r[i];
}
for(int i=1;i<=m;i++){
scanf("%d%d%d",&a[i],&b[i],&c[i]);
}
//二分处理
int l=1,r=m;
while(l<r){
int mid=(l+r)/2;
if(check(mid)) r=mid;
else l=mid+1;
}
if(check(l))
printf("-1\n%d\n",l);
else printf("0");
return 0;
}