acwing-寒假每日一题

寒假每日一题

二分

1.技能升级

#include<iostream>
#include<algorithm>
#include<cmath>
#define int long long
using namespace std;
int n,m;
int res=0;
int ans;
int result;
pair<int,int> a[100020];
int check(int mid){
    int cnt=0;
    for(int i=0;i<n;i++){
        if(a[i].first>mid){
            cnt+=ceil((double)(a[i].first-mid)/(a[i].second));
        }
    }
    return cnt<=m;
}
int sum(int a,int c,int b){
    int m=a-b*(c-1);
    return (m+a)*c>>1;
}
signed main(){
    cin>>n>>m;
    for(int i=0;i<n;i++){
        cin>>a[i].first>>a[i].second;
    }
    int l=0,r=1e6+10;
    while(l<r){
        int mid=l+r>>1;
        if(check(mid)){
            r=mid;
        }
        else{
            l=mid+1;
        }
    }
    result=m;
    for(int i=0;i<n;i++){
        //先求次数在求和
        if(a[i].first>l){
            ans=ceil((double)(a[i].first-l)/(a[i].second));
            result-=ans;
            res+=sum(a[i].first,ans,a[i].second);
        }

    }
    cout<<res+result*l;
    return 0;
}

对于浮点数,向上取整ceil,向下取整floor,四舍五入round
二分模板一共有两个,分别适用于不同情况。
算法思路:假设目标值在闭区间[l, r]中, 每次将区间长度缩小一半,当l = r时,我们就找到了目标值。

yxc版本1

当我们将区间[l, r]划分成[l, mid][mid + 1, r]时,其更新操作是r = mid或者l = mid + 1;,计算mid时不需要加1C++ 代码模板:
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    return l;
}

yxc版本2

当我们将区间[l, r]划分成[l, mid - 1][mid, r]时,其更新操作是r = mid - 1或者l = mid;,此时为了防止死循环,计算mid时需要加1C++ 代码模板:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

前缀和

统计子矩阵

#include<iostream>
using namespace std;
int arr[510][510];
long long res=0;
int main(){
    int n,m;cin>>n>>m;
    int k;cin>>k;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>arr[i][j];
        }
    }
     for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            arr[i][j]=arr[i][j]+arr[i-1][j]+arr[i][j-1]-arr[i-1][j-1];
            
        }
    }
    for(int i=1;i<=m;i++){
        for(int j=i;j<=m;j++){
            for(int r=1,l=1;r<=n;r++){
                while(l<=r&&(arr[r][j]-arr[r][i-1]-arr[l-1][j]+arr[l-1][i-1])>k)l++;
                if(l<=r) res+=(r-l+1);
            }
        }
    }
    cout<<res;
    return 0;
}

这里除了前缀和,还重新优化了一下思路

 for(int i=1;i<=m;i++){
        for(int j=i;j<=m;j++){
            for(int r=1,l=1;r<=n;r++){
                while(l<=r&&(arr[r][j]-arr[r][i-1]-arr[l-1][j]+arr[l-1][i-1])>k)l++;
                if(l<=r) res+=(r-l+1);
            }
        }
    }

其中i和j是该矩阵的列,内部循环利用双指针,根据矩阵和递增的原理
递增三元组
二分法版本

#include<iostream>
#include<algorithm>
using namespace std;
long long  res=0;int n;int arr[4][100010];
int check(int m,int x){
    int l=1;int r=n;
    while(l<r){
        int mid=l+r>>1;
        if(arr[x+1][mid]>m){
            r=mid;
        }
        else l=mid+1;
    }
    return l;
}
int main(){
    cin>>n;
    for(int i=1;i<=3;i++){
        for(int j=1;j<=n;j++){
            cin>>arr[i][j];
        }
    }
    for(int i=1;i<=3;i++){
        sort(arr[i]+1,arr[i]+n+1);
    }

        for (int i = 1; i <= n; i ++ ){
            int tmp=arr[2][i];
            int x=lower_bound(arr[1]+1,arr[1]+n+1,tmp)-arr[1]-1;
            int y=upper_bound(arr[3]+1,arr[3]+n+1,tmp)-arr[3];
            if(x>=1&&y<=n)
            res+=(long long)x*(n-y+1);
        }
    
    
    cout<<res;
    return 0;
}

暴力做法本题需要枚举三行,复杂度是n3,肯定会超时,那么我们先理解他的题意:三个符合匹配条件的数a<b<c,那么我们可以先将这三行序列排序最后相乘就能求出结果

差分

1.空调
ac code

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int arr[N];
int mx=0,mi=0;
int main(){
    int n;cin>>n;
    for (int i = 1; i <= n; i ++ ){
        cin>>arr[i];
    }
    for (int i = 1; i <= n; i ++ ){
        int a;cin>>a;
        arr[i]=arr[i]-a;
    }
    //构造差分数组
    //这个为什么要从后往前构造呢,因为他是在原数组中进行变换,如果
    //从第一个开始构造,那么后面的会相互影响,所以从后面构造不会影响
    for (int i = n; i >1; i -- ){
        arr[i]-=arr[i-1];
    }
    //对于差分数组,求得最大次数
    for (int i = 1; i <= n; i ++ ){
        if(arr[i]>0)mx+=arr[i];
        else mi-=arr[i];
    }
    cout<<max(mi,mx)<<endl;
    return 0;
}

这一题主要在于分析思路,要求最优改变几次区间温度,能使每个牛栏获得最佳温度,那么我们可以对差值数组求差分,当差分数组为零时,那么即为最合适温度,要使差分数组为零,要么整个区间加或减,要么对单个区间进行操作
差分矩阵
ac code

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int arr[1010][1010];int b[1010][1010];
void insert(int x1,int y1,int x2,int y2,int x){
    b[x1][y1]+=x;
    b[x1][y2+1]-=x;
    b[x2+1][y1]-=x;
    b[x2+1][y2+1]+=x;
}

int main(){
    int n,m;cin>>n>>m;
    int k;cin>>k;
    for (int i = 1; i <= n; i ++ ){
        for (int j = 1; j <= m; j ++ ){
            cin>>arr[i][j];
        }
    }
    //创建差分矩阵
    
    for (int i = 1; i <= n; i ++ ){
        for (int j = 1; j <= m; j ++ ){
            b[i][j]=arr[i][j]-arr[i-1][j]-arr[i][j-1]+arr[i-1][j-1];
        }
    }
    //对于每个样例插入差分矩阵
    while(k--){
        int x1,y1,x2,y2,x;
        cin>>x1>>y1>>x2>>y2>>x;
        
        insert(x1,y1,x2,y2,x);
    }
    //输出改变后的矩阵
    for (int i = 1; i <= n; i ++ ){
        for (int j = 1; j <= m; j ++ ){
            b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1];
            cout<<b[i][j]<<" ";
        }
        cout<<endl;
    }
    
    return 0;
}

差分的重点不在于差分数组的创建,而在于差分数组的维护,这里用一个图形便可以容易理解
在这里插入图片描述
代码是这一段

void insert(int x1,int y1,int x2,int y2,int x){
    b[x1][y1]+=x;
    b[x1][y2+1]-=x;
    b[x2+1][y1]-=x;
    b[x2+1][y2+1]+=x;
}

原题链接
棋盘
ac代码

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int arr[2010][2010];
void chance(int x1,int y1,int x2,int y2){
    arr[x1][y1]++;
    arr[x1][y2+1]--;
    arr[x2+1][y1]--;
    arr[x2+1][y2+1]++;
}
int main(){
    //先创建数组
    int n,m;cin>>n>>m;
    //构造出差分矩阵
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            arr[i][j]=arr[i][j]-arr[i-1][j]-arr[i][j-1]+arr[i-1][j-1];
        }
    }
    while(m--){
        int x1,y1,x2,y2;
        cin>>x1>>y1>>x2>>y2;
        chance(x1,y1,x2,y2);
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            arr[i][j]=arr[i-1][j]+arr[i][j-1]+arr[i][j]-arr[i-1][j-1];
            if(arr[i][j]%2==1)cout<<1;
            else cout<<0;
        }
        cout<<endl;
    }
    return 0;
}

我的疑问是,差分只能对范围内的区间在o1的时间复杂度内同时加上或减去一个数,那么如何把该范围内的1变为0,0变为1呢?
答:根据题意,我们可以分析得,改变次数为奇数次的时候,棋盘都为黑子,改变次数为偶数次的时候,棋盘都为白子,得到这个结论,我们就很容易ac了

双指针

牛的学术圈
本题主要是二分
ac代码

#include<iostream>
using namespace std;
int n,backup;
int arr[100010];
int check(int mid){
    int backupp=backup;
   
    int cnt=0;
    for(int i=0;i<n;i++){
        if(arr[i]>=mid){
            cnt++;
        }
        else if(backupp&&arr[i]+1>=mid){
            cnt++;
            backupp--;
        }
        
    }return cnt>=mid;
}
int main(){
    ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    cin>>n>>backup;

    for(int i=0;i<n;i++)cin>>arr[i];
    int l=0,r=1e6;
    while(l<r){
        int mid=l+r+1>>1;
        if(check(mid)){
            l=mid;
        }
        else {
            r=mid-1;
        }
    }
    cout<<l;
    return 0;
}

日志统计
ac代码

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int ans[100010];bool mm[100010];
pair<int,int> arr[100010];
int main(){
    int a,b,c;cin>>a>>b>>c;//a样例个数,b时间,c点赞数
    for(int i=0;i<a;i++){
        cin>>arr[i].first>>arr[i].second;
    }
 
    sort(arr,arr+a);//对first进行排序
    for(int i=0,j=0;i<a;i++){
        int tmp=arr[i].first;
        ans[arr[i].second]++;
        while(tmp-arr[j].first>=b){
            ans[arr[j].second]--;
            j++;
        }
        if(ans[arr[i].second]>=c){
            
            mm[arr[i].second]=true;
        }
        //如果超出这段区间
        //如何统计热帖,他不是从一开始
        //那就遍历一个统计一个
    }
    for (int i = 0; i <= 100000; i ++ ) if (mm[i]) cout << i << endl;
    return 0;
}

做题时的疑问点:
1.时间不连续,应该如何存储呢,数据较小,可以直接存储
2.i++,走在前面,一旦i-j大于时间差,那么j就往前走,并实时更新

  • 20
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shix .

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值