[NEFU ACM大一暑假集训 解题报告]前缀和与差分
Posted 鱼竿钓鱼干
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[NEFU ACM大一暑假集训 解题报告]前缀和与差分相关的知识,希望对你有一定的参考价值。
[NEFU ACM大一暑假集训 解题报告]前缀和与差分
题量略大,所以解题报告和fjy大佬分了一下工
由我负责A-K部分题解(不是AK部分题解啊,哈哈)
后半部分题解(LM+R~V+XYZ)由fjy大佬发布
剩下的题目烦请有余力的同学自行解决
题目
A - Molly’s Chemicals
题目求区间和为m的幂次的子区间个数,也就是要满足下面这个公式。
s
u
m
[
r
]
−
s
[
l
−
1
]
=
m
k
(
k
>
=
0
)
sum[r]-s[l-1]=m^k(k>=0)
sum[r]−s[l−1]=mk(k>=0)
幂次的话想不到啥特性,但是这题m是固定的那么直接把幂次暴力预处理出来就完事了。
2
64
2^{64}
264大概为1e19级别,题目弄到1e14就够了,所以弄个几十次就能结束。
然后对于这种找满足条件的等式,我们可以很快想到用map来查询有没有配对的值。但是这里的map需要采用类似前缀和的方式来维护查询,而不是直接在1~n里面搜。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int>PII;
#define endl '\\n'
int T;
int n,m;
const int N=1e5+10;
LL sum[N],mpow[N];
map<LL,LL>mp;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>sum[i];
sum[i]+=sum[i-1];
}
int cnt=0;
LL mk=1;
if(m==1)mpow[++cnt]=1;
else if(m==-1){mpow[++cnt]=1;mpow[++cnt]=-1;}
else{
while(abs(mk)<1e14+10){
mpow[++cnt]=mk;
mk*=m;
}
}
LL ans=0;
mp[0]++;
for(int i=1;i<=n;i++){
for(int j=1;j<=cnt;j++){
if(mp.find(sum[i]-mpow[j])!=mp.end())ans+=mp[sum[i]-mpow[j]];
}
mp[sum[i]]++;
}
cout<<ans<<endl;
return 0;
}
B - Balanced Substring
找一个最长的连续子串要求0和1的数量相同。
对于数量相同而且只有01两种,我们一般把0和1做差,预处理前缀和。题目要求0和1数量相同那么就是满足条件:
s
u
m
[
r
]
−
s
u
m
[
l
−
1
]
=
0
sum[r]-sum[l-1]=0
sum[r]−sum[l−1]=0
而我们要求的最长的连续字串,考虑区间
[
i
,
j
]
[i,j]
[i,j]对答案的贡献为
i
−
j
+
1
i-j+1
i−j+1我们固定右端点i,然后找最小的满足条件的j,使得长度最大。
类似A题,我们用map维护i前面,满足条件的j的最小值。维护满足条件的最小值,只需要从前往后遍历一边,把第一个满足条件的标记即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int>PII;
#define endl '\\n'
const int N=100005;
int T;
int t[N];
map<int,int>mp;
int main(){
int n;cin>>n;
string s;cin>>s;
if(s[0]=='1')t[0]=1;
else t[0]=-1;
for(int i=1;s[i];i++){
if(s[i]=='1')t[i]=t[i-1]+1;
else t[i]=t[i-1]-1;
}
for(int i=0;i<n;i++){
if(mp.find(t[i])==mp.end()){
mp[t[i]]=i;
}
}
int ans=0;
for(int i=0;i<n;i++){
if(mp[t[i]]!=i)ans=max(ans,i-mp[t[i]]);
if(t[i]==0)ans=max(ans,i+1);
}
cout<<ans;
return 0;
}
C - White Lines
行和列没分开处理,写公式写吐了,后来看别人的发现分开处理会好很多
参考这篇
n
∗
n
n*n
n∗n大小的网格,有黑白两种颜色,要求使用
k
∗
k
k*k
k∗k的橡皮擦擦一次,求最多的白线(全为白的行和列)数目。
首先读入地图,黑色为1,白色为0。
最暴力的方法是枚举橡皮擦起点,然后
O
(
N
)
O(N)
O(N)判是否构成白线算贡献,复杂度为
O
(
N
3
)
O(N^3)
O(N3)
考虑优化算贡献的部分:
我们可以预处理两个数组row[i][j],col[i][j]表示i行j列左边和上边分别有多少黑块
我们先把本来就是白线的部分算出来
条件为:
if(row[i][n]==0)cnt++;
if(col[i][n]==0)cnt++;
然后前缀和维护覆盖后的白线数列
r
u
m
[
i
]
[
j
]
rum[i][j]
rum[i][j]维护的是前i行第j个字符到第
j
+
k
−
1
j+k-1
j+k−1个字符被覆盖后,组成的白线数量
c
u
m
[
i
]
[
j
]
cum[i][j]
cum[i][j]维护的是前j列第i个字符到第
i
+
k
−
1
i+k-1
i+k−1个字符被覆盖后,组成的白线数量
如何判断一段被覆盖后这行/列为白线?这一段区间内黑块数量==整行/列黑块数列
最后枚举橡皮擦左上角起点就可以了
区域的贡献为
r
s
u
m
[
i
+
k
−
1
]
[
j
]
−
r
s
u
m
[
i
−
1
]
[
j
]
+
c
s
u
m
[
j
+
k
−
1
]
[
i
]
−
c
s
u
m
[
j
−
1
]
[
i
]
rsum[i+k-1][j]-rsum[i-1][j]+csum[j+k-1][i]-csum[j-1][i]
rsum[i+k−1][j]−rsum[i−1][j]+csum[j+k−1][i]−csum[j−1][i]
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int>PII;
#define endl '\\n'
int T;
const int N=2005;
char a[N][N];
int g[N][N],row[N][N],col[N][N];
int rsum[N][N],csum[N][N];
int main(){
int n,k;cin>>n>>k;
for(int i=1;i<=n;i++)scanf("%s",a[i]+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
row[i][j]+=row[i][j-1]+(a[i][j]=='B');
col[j][i]+=col[j][i-1]+(a[i][j]=='B');
}
LL cnt=0;
for(int i=1;i<=n;i++){
if(row[i][n]==0)cnt++;
if(col[i][n]==0)cnt++;
}
for(int i=1;i<=n;i++){
for(int j=1;j+k-1<=n;j++){
rsum[i][j]=rsum[i-1][j]+(row[i][j+k-1]-row[i][j-1]==row[i][n]&&row[i][n]!=0);
csum[i][j]=csum[i-1][j]+(col[i][j+k-1]-col[i][j-1]==col[i][n]&&col[i][n]!=0);
}
}
LL ans=0;
for(int i=1;i+k-1<=n;i++){
for(int j=1;j+k-1<=n;j++){
ans=max(ans,cnt+rsum[i+k-1][j]-rsum[i-1][j]+csum[j+k-1][i]-csum[j-1][i]);
}
}
cout<<ans<<endl;
return 0;
}
D - Monitor
n×m 区域,有p个长方形,q次询问,每次询问一个区域能否完全地被p个长方形覆盖。
本题卡空间,所以使用vector来写,当然也可以采用坐标hash+map的方式压缩空间。
实际上vector目前在大部分评测平台上,性能和数组基本差不了太多,开完
O
(
2
)
O(2)
O(2)优化后基本一致
思路比较简单,先用二维差分标记p个长方形,然后通过一次前缀和预处理出来。
判断一个区域是否被完全覆盖,我们可以把被覆盖的点都赋值为1,若这个区域被全部覆盖,那么这个区域的区间和等于这个区域的面积。
因此在第一步前缀和预处理后,我们还需要把被覆盖的点值处理为1
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int>PII;
#define endl '\\n'
int T;
int main(){
int n,m,p;
while(cin>>n>>m>>p){
vector<vector<LL>> a(n + 5, vector<LL>(m + 5, 0));
while(p--){
int x1,x2,y1,y2;scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
a[x1][y1]++;a[x2+1][y1]--;a[x1][y2+1]--;a[x2+1][y2+1]++;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++以上是关于[NEFU ACM大一暑假集训 解题报告]前缀和与差分的主要内容,如果未能解决你的问题,请参考以下文章