[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[l1]=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[l1]=0
而我们要求的最长的连续字串,考虑区间 [ i , j ] [i,j] [i,j]对答案的贡献为 i − j + 1 i-j+1 ij+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 nn大小的网格,有黑白两种颜色,要求使用 k ∗ k k*k kk的橡皮擦擦一次,求最多的白线(全为白的行和列)数目。
首先读入地图,黑色为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+k1个字符被覆盖后,组成的白线数量
c u m [ i ] [ j ] cum[i][j] cum[i][j]维护的是前j列第i个字符到第 i + k − 1 i+k-1 i+k1个字符被覆盖后,组成的白线数量

如何判断一段被覆盖后这行/列为白线?这一段区间内黑块数量==整行/列黑块数列

最后枚举橡皮擦左上角起点就可以了
区域的贡献为 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+k1][j]rsum[i1][j]+csum[j+k1][i]csum[j1][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大一暑假集训 解题报告]前缀和与差分的主要内容,如果未能解决你的问题,请参考以下文章

[NEFU ACM大一暑假集训 解题报告]字典树

[NEFU ACM] 2020级暑期训练 解题报告

2018年暑假ACM个人训练题9(动态规划)解题报告

acm经验(转)

2017暑假集训前总结和规划

停更五月,而今再出发