暑期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)\)。
带入公式:
移项得:
令\(P_i = sum_i - i\),原式化为:
因此只需要两个位置\(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\).
于是我们有转移方程:
在转移的过程中记录最大值即可。
#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\)数组即可。
#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\)家住户的总疲劳值公式为:
如果我们忽略距离\(s_i\),显然应该按疲劳值\(a_i\)从大到小贪心选择
对于\(X\)家住户,我们有两种策略:
- 直接选\(a_i\)最大的前\(X\)加住户。
- 在前 \(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;
}
浙公网安备 33010602011771号