暑假集训刷题-week1(7.3-7.9)

👍天再高又怎样,踮起脚尖就更接近阳光。以后的你会感谢现在每天坚持努力奋斗的自己。👍

写在前面:暑期在校集训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;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值