【vjudge训练记录】大一寒假专项训练——前缀和/差分

训练情况

A题

前缀和模板题,我们输入完 \(a_i\) 后直接求前缀和 \(a_i = a_i + a_{i-1}\),求区间 \([l,r]\) 的和就为 \(a_r-a_{l-1}\)

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define endl '\n'

using namespace std;

void solve(){
    int n,m;
    cin>>n>>m;
    vector<int> a(n + 1);
    for(int i = 1;i<=n;i++) cin>>a[i];
    for(int i = 2;i<=n;i++) a[i] += a[i-1];
    while(m--){
        int l,r; cin>>l>>r;
        cout<<a[r] - a[l-1]<<endl;
    }
}

signed main(){
    // int T; cin>>T; while(T--)
    solve();
    return 0;
}

B题

差分模板题,我们记 \(p_i = a_i - a_{i-1}\),区间加只需要在 \(p_l += k,p_{r+1} -= k\),最后再求一遍前缀和即可

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define endl '\n'

using namespace std;

void solve(){
    int n,m; cin>>n>>m;
    vector<int> a(n + 1),p(n + 1);
    for(int i = 1;i<=n;i++) cin>>a[i];
    for(int i = 1;i<=n;i++) p[i] = a[i] - a[i-1];
    while(m--){
        int l,r,k; cin>>l>>r>>k;
        p[l]+=k;
        p[r+1]-=k;
    }  
    for(int i = 1;i<=n;i++) p[i] += p[i-1];
    for(int i = 1;i<=n;i++) cout<<p[i]<<" ";
}

signed main(){
    // int T; cin>>T; while(T--)
    solve();
    return 0;
}

C题

\(n\) 个数里头尾删掉 \(k\) 个数,可以等价为连续选 \(n-k\) 个连续的数求和,区间求和问题我们可以使用前缀和预处理

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define endl '\n'

using namespace std;

void solve(){
    int n,k; cin>>n>>k;
    vector<int> a(n + 1);
    for(int i = 1;i<=n;i++) cin>>a[i];
    for(int i = 1;i<=n;i++) a[i] += a[i-1];
    int ans = 0;
    for(int i = 1;i+n-k-1<=n;i++) ans = max(ans,a[i+n-k-1] - a[i-1]);
    cout<<ans<<endl;
}

signed main(){
    // int T; cin>>T; while(T--)
    solve();
    return 0;
}

D题

二维差分再前缀和,输入矩阵的左上角 \((xa,ya)\) 右下角 \((xb,yb)\),其中我们差分数组 \((xa,ya)\) 加一,即从 \((xa,ya)\)\((n,n)\) 的地毯数全部加一,所以我们差分数组 \((xb+1,yb+1)\) 减一,即从 \((xb+1,yb+1)\)\((n,n)\) 的地毯数全部减一,剩下了两个区域 \((xb+1,ya),(xa,yb+1)\) 再减一,最后再求一遍二维前缀和查询即可

image

点击查看代码
#include <bits/stdc++.h>
// #define int long long
#define endl '\n'

using namespace std;

void solve(){
    int n,m; cin>>n>>m;
    vector<vector<int>> a(n + 2,vector<int>(n + 2));
    while(m--){
        int xa,ya,xb,yb; cin>>xa>>ya>>xb>>yb;
        ++a[xa][ya];
        ++a[xb+1][yb+1];
        --a[xb+1][ya];
        --a[xa][yb+1];
    }
    for(int i = 1;i<=n;i++){
        for(int j = 1;j<=n;j++){
            a[i][j] += a[i][j-1];
        }
    }
    for(int i = 1;i<=n;i++){
        for(int j = 1;j<=n;j++){
            a[i][j] += a[i-1][j];
        }
    }
    for(int i = 1;i<=n;i++){
        for(int j = 1;j<=n;j++){
            cout<<a[i][j]<<" ";
        }
        cout<<endl;
    }
}

signed main(){
    // int T; cin>>T; while(T--)
    solve();
    return 0;
}

E题

选取当前卡牌到最左边实际上就是区间 \([1,r]\) 求和,直接前缀和处理,想要答案最大,前缀和贡献只能为正,至少选择两张,所以要从二开始

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define endl '\n'

using namespace std;

void solve(){
    int n; cin>>n;
    vector<int> a(n + 1);
    for(int i = 1;i<=n;i++) cin>>a[i];
    for(int i = 1;i<=n;i++) a[i] += a[i-1];
    int ans = 0;
    for(int i = 2;i<=n;i++) if(a[i] > 0) ans += a[i];
    cout<<ans<<endl;
}

signed main(){
    // int T; cin>>T; while(T--)
    solve();
    return 0;
}

F题

等差数列差分一次还是有公差 d 的存在,所以我们再差分一次就可以做到 \(O(1)\) 更新了,差分后的结果如上图,最后跑两边前缀和就是答案

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define endl '\n'

using namespace std;

void solve(){
    int n,m; cin>>n>>m;
    vector<int> a(n + 5);
    while(m--){
        int l,r,s,e; cin>>l>>r>>s>>e;
        int res = (e - s) / (r - l);
		a[l] += s;a[l+1]+=(res-s);
		a[r+1] -= (res + e);a[r+2] += e;
    }
    for(int i = 1;i<=n;i++) a[i] += a[i-1];
    for(int i = 1;i<=n;i++) a[i] += a[i-1];
    int ans = 0,ma = 0;;
    for(int i = 1;i<=n;i++) ans ^= a[i],ma = max(ma,a[i]);
    cout<<ans<<" "<<ma<<endl;
}

signed main(){
    // int T; cin>>T; while(T--)
    solve();
    return 0;
}

G题

直接枚举区间 \([l,r]\) 的时间复杂度为 \(O(n^2)\) 会超时,对于区间求和的问题我们容易想到前缀和,但是我们需要处理区间是 \(k\) 的倍数,我们考虑对前缀和对 \(k\) 取模(取余数),如果位置 \(l,r\) 的前缀和 \((p_l \mod k) = (p_r \mod k)\) 则说明区间 \([l,r]\) 的和一定是 \(k\) 的倍数,因为 \((x \mod b) = ((x + kb) \mod b)\),所以我们需要统计 \(p_i\) 余数的出现次数,遍历的时候查询当前位置余数在前面出现了几次,就是有几个区间,答案加上区间个数即可,注意一下初始条件,详情见代码

点击查看代码
#include <bits/stdc++.h>
#define int long long
#define endl '\n'

using namespace std;

void solve(){
	int n,k; cin>>n>>k;
    int a[n+1],pre[n+1],cnt[n+1];
    pre[0] = 0;
	for(int i = 1;i<=n;i++) cin>>a[i],cnt[i] = 0;
	for(int i = 1;i<=n;i++) pre[i] = pre[i-1] + a[i];
	for(int i = 1;i<=n;i++) pre[i] %= k;
	cnt[0] = 1;
    int ans = 0;
	for(int i = 1;i<=n;i++){
		ans += cnt[pre[i]];
		cnt[pre[i]]++;
	}
	cout<<ans<<endl;
}

signed main(){
    // int T; cin>>T; while(T--)
    solve();
    return 0;
}

H题

https://www.luogu.com.cn/article/0kz22o0v

posted @   MNNUACM_2024ZY  阅读(41)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示