暑期CSP模拟第一场 前缀和

CSP暑期模拟第一场 前缀和

A Static Range Sum

给定序列\(A\)以及\(Q\)次询问,每次询问给定\(l,r\), 求 \(\sum_{i = l}^r A_i\)

前缀和模板,注意到序列\(A\)并不会更改,只需要记录每个元素及之前的和即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int sum[500005];
signed main(){
    int n,q; cin >> n >> q;
    for(int i = 1; i <= n; i++){
        cin >> sum[i];
        sum[i] += sum[i-1];
    }
    while(q--){
        int l,r; cin >> l >> r;
        cout << sum[r] - sum[l] <<"\n";
    }
}

B Breed Counting

给定每只奶牛的品种(\(1/2/3\)),以及\(Q\)次询问,每次查询\([l,r]\)区间中每种品种的奶牛数量。

与A题同理,开三个前缀和数组维护每种奶牛的前缀和即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int sum[1000005][5];
signed main(){
    freopen("bcount.in","r",stdin);
    freopen("bcount.out","w",stdout);
    int n,q; cin >> n >> q;
    for(int i = 1; i <= n; i++){
        int t; cin >> t;
        sum[i][t]++;
        for(int j = 1; j <= 3; j++){
            sum[i][j] += sum[i-1][j];
        }
    }
    while(q--){
        int l,r; cin >> l >> r;
        cout << sum[r][1] - sum[l-1][1] << " " << sum[r][2] - sum[l-1][2] << " " << sum[r][3] - sum[l-1][3] << "\n";
    }
    return 0;
}

C GCD on Blackboard

给定\(n\)个整数的序列\(A\).选择并替换一个数字,使得剩下的公约数最大。

考虑将替换转化为删除,因为替换,我们可以讲任何数替换为\(1\),这样能保证是所有数字的因数。

枚举断点(删除的点),开两个前缀和数组,一个记录\([1,k-1]\),一个记录\([k+1,n]\),我们有剩余的GCD = \(\gcd(\text{sum1}[i-1],\text{sum2}[i+1]).\)

#include<bits/stdc++.h>
using namespace std;
int a[100005];
int sum1[100005],sum2[100005];
int main(){
    int n; cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
    }
    sum1[1] = a[1];
    sum1[0] = sum2[0] = 1;
    sum1[n+1] = sum2[n+1] = 1;
    for(int i = 2; i <= n; i++){
        sum1[i] = __gcd(a[i],sum1[i-1]);
    }
    sum2[n] = a[n];
    for(int i = n-1; i >= 1; i--){
        sum2[i] = __gcd(a[i],sum2[i+1]);
    }
    int ans = -1;
    for(int i = 1; i <= n; i++){
        int x1 = __gcd(sum1[i-1],sum1[1]);
        int x2 = __gcd(sum2[i+1],sum2[n]);
        ans = max({ans,__gcd(x1,x2)});
        if(i == 1) ans = max(ans,x2);
        if(i == n) ans = max(ans,x1);
    }
    cout << ans << "\n";
    
    
}

D Good Subarrays

给定一个数组,满足元素\(\in [0,9]\),且是整数,当且仅当子数组的元素满足\(\sum_{i=l}^r a_i = r-l+1\).时是好子数组。问有多少个好子数组。

利用前缀和数组\(sum_i = \sum_{k=1}^i a_k\),区间\([l,r]\)的和可以表示为\(sum_r - sum_{l-1}\),区间长度为\(r-(l-1)\)

带入公式:

\[sum_r - sum_{l-1} = r-(l-1). \]

移项得:

\[sum_r - r = sum_{l-1} - (l-1). \]

\(P_i = sum_i - i\),原式化为:

\[P_r = P_{l-1}. \]

因此只需要两个位置\(r\)\(l-1\)\(P\)值相等,区间\([l,r]\)就是好子数组。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int off = 100000;
int sum[100005],a[100005];
int cnt[100005];
int st = off;
map <int,int> mp;
signed main(){
    int t; cin >> t;
    while(t--){
        int n; cin >> n;
        mp.clear();
        for(int i = 0; i <= n; i++){
            sum[i] = a[i] = cnt[i] = 0;
        }
        st = off;
        for(int i = 1; i <= n; i++){
            char ch; cin >> ch;
            a[i] = ch - '0';
            sum[i] = a[i] + sum[i-1];
        }
        for(int i = 0; i <= n; i++){
            mp[sum[i]-i]++;
        }
        int ans = 0;
        for(auto t : mp){
            ans += t.second * (t.second-1) /2;
        }
        cout << ans << "\n";
    }
}

E Maximum Subarray Sum

给定\(n\)个整数的数组,求其最大连续字段和。

DP解决即可,对于每个数字,我们可以选或不选。选择就将其加入当前维护的字段和中,不选择就将当前字段和设为\(0\).

于是我们有转移方程:

\[dp_i = max(dp_{i-1}+a_i,a_i). \]

在转移的过程中记录最大值即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[200005];
int dp[200005];
signed main(){
    int n; cin >> n;
    for(int i = 1; i <= n; i++){
        cin >> a[i];
    }
    int ans = -1e18;
    for(int i = 1; i <= n; i++){
        dp[i] = max(dp[i-1]+a[i],a[i]);
        ans = max(ans,dp[i]);
    }
    cout << ans << "\n";
}

F Forest Queries

给定\(n \times n\)的森林地图,.表示空地,*表示树木。有\(q\)次询问,给定\(x_1,y_1,x_2,y_2\),求以\(x_1,y_1\)\(x_2,y_2\)为对角的矩形区域中的树木数量。

二维前缀和模板,令\(sum_{i,j}\)为以\((i,j)\)为右下角,以\((1,1)\)为左上角矩形中的树木数量,根据容斥原理,我们有:

\[sum_{i,j} = sum_{i-1,j} + sum_{i,j-1} - sum_{i-1,j-1} + a_{i,j}. \]

递推求出\(sum\)数组即可。

#include<bits/stdc++.h>
using namespace std;
int mp[1005][1005];
int sum[1005][1005];
int main(){
    int n,q; cin >> n >> q;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            char ch; cin >> ch;
            if(ch == '*') mp[i][j]++;
        }
    }
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= n; j++){
            sum[i][j] = sum[i][j-1] + sum[i-1][j] - sum[i-1][j-1] + mp[i][j];
        }
    }
    while(q--){
        int sx,sy,ex,ey; cin >> sx >> sy >> ex >> ey;
        cout << sum[ex][ey] - sum[ex][sy-1] - sum[sx-1][ey] + sum[sx-1][sy-1] <<"\n";
    }
    return 0;
}

G 推销员

某街长为\(n\)米,推销员每走\(1\)米就会增加\(1\)的疲劳值,每家住户有距离路口的距离\(s_i\)与推销疲劳值\(a_i\),推销员全部推销完,会原路返回。

现在依次向\(X\)个住户推销,对于不同的\(X\),求最大疲劳值。

易推出推销\(X\)家住户的总疲劳值公式为:

\[\text{总疲劳值} = \sum \text{选中的} A_i + 2 \times \max(\text{选中的}S_i) \]

如果我们忽略距离\(s_i\),显然应该按疲劳值\(a_i\)从大到小贪心选择

对于\(X\)家住户,我们有两种策略:

  1. 直接选\(a_i\)最大的前\(X\)加住户。
  2. 在前 \(X-1\) 家选 \(a_i\) 最大的,第 \(X\) 家去更远的地方选一个“距离极远”的住户

通过前缀和优化,对于每个\(X\),答案就是这两种情况取最大值。

#include<bits/stdc++.h>
using namespace std;
int n,sum[100010],q[100010],h[100010];
struct node{
    int s;
    int a;
}v[100010];
bool cmp(node x,node y){return x.a>y.a;}
int main(){
    cin >> n;
    for(int i = 1;i <= n; i++) cin >> v[i].s;
    for(int i = 1;i <= n; i++) cin >> v[i].a;
    sort(v + 1,v + 1 + n,cmp);
    for(int i = 1;i <= n; i++) sum[i] = sum[i-1] + v[i].a; 
    for(int i = 1;i <= n; i++) q[i] = max(q[i-1],2*v[i].s);

    for(int i = n;i >= 1;i--) h[i] = max(h[i+1],2*v[i].s+v[i].a);
    for(int i = 1; i <= n; i++){
        cout << max(sum[i]+q[i],sum[i-1]+h[i]) << "\n";
    }
    return 0;
}

H 等价消除

给定一个字符串\(S\),每次可以删去其中两个字符的方式,求\(S\)有多少字串可以被消除成空串。

不难发现只有当所有字符出现次数为偶数时才能被消除成空串。所以我们的目标就是找到有多少字串是满足“所有字符都是偶数次”。

若直接枚举\(l,r\),时间复杂度为\(O(n^2)\).考虑使用前缀和优化。
既然满足每个字符的出现次数为偶数次,因为偶数 - 偶数 = 偶数; 奇数 - 奇数 = 奇数。

也就是说:只要前缀 \(r\) 中某个字符的奇偶性,和前缀 \(l-1\) 中该字符的奇偶性相同,那么区间 \([l, r]\) 中该字符的数量就一定是偶数。

使用哈希表map存储26个字母的奇偶性即可。

#include<bits/stdc++.h>
using namespace std;
int cnt[30];
map <int,int> m;
// 1:奇数 2:偶数 
long long ans,x;
int main(){
	int len; cin >> len;
	string s; cin >> s;
	m[0] = 1;
	for(int i = 0; i < len; i++){
		long long t = s[i] - 'a';
		cnt[t] = (cnt[t]+1) % 2;
		x ^= (1 << t);
		ans += m[x];
		m[x]++;
	}
	cout << ans;
	
	return 0;
} 
posted @ 2026-08-31 17:20  yunyan_ads  阅读(9)  评论(0)    收藏  举报