前缀和/差分

前缀和

顾名思义:用某一个数组来记录数组a前i项和,这个还可以用来求区间[l,r]的和:s[r]-s[l-1](因为第l项也在区间内)

话不多说,直接上例题:LeetCode 5393

  • 首先,暴力搜索,TLE。
  • 很明显,左边拿的+右边拿的=k张,且拿了前面的才能那拿面的,有很明显的前缀和后缀性质,枚举左边拿的张数i,对应的右边只能拿k-i张,线性时间。
  • 且枚举后缀的时候有个技巧,并不需要真正的后缀,具体看代码。

AC代码:

const int MAXN = 1e5 + 5;
class Solution {
public:
    int pre[MAXN],suf[MAXN];
    int maxScore(vector<int>& cardPoints, int k) {
        int n = cardPoints.size();
        memset(pre,0,sizeof(pre));
        memset(suf,0,sizeof(suf));
        for(int i = 1;i <= n;++i){
            pre[i] += (pre[i - 1] + cardPoints[i - 1]);
            suf[i] += (suf[i - 1] + cardPoints[n - i]); //!!!
        }
        int ans = 0;
        for(int i = 0;i <= k;++i){ //枚举左边拿的张数
            ans = max(ans,pre[i] + suf[k - i]);
        }
        return ans;
    }
};

前缀和与差分

传送门:洛谷P3397

  • 此题,假如暴力,枚举每个点,再枚举每个区间,试想下最坏情况,每个区间都是整张图,那就是O(nn m n n),明显TLE。
  • 试着做下预处理,优化一下:先枚举所有区间,给区间内的点加上1表示被一个毯子覆盖了,然后再遍历下所有点。第一步时间复杂度最坏为O(mn2),第二步为O(n2),总的为O(mn2),照理说n=m=103,应该过不了,可能数据水了点。

下面贴下AC代码(后面有利用差分的优化):

#include
#include
#include
using namespace std;
const int maxn = 1000 + 5;
int n,m;
int ans[maxn][maxn];

int main(void){
    scanf("%d%d",&n,&m);
    memset(ans,0,sizeof(ans));
    int x1,y1,x2,y2;
    for(int i = 0;i < m;++i){
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        for(int x = x1;x <= x2;++x){
            for(int y = y1;y <= y2;++y) ans[x][y]++;
        }
    }
     
    for(int x = 1;x <= n;++x){
        for(int y = 1;y <= n;++y){
            if(y == 1) printf("%d",ans[x][y]);
            else printf(" %d",ans[x][y]);
        }
        printf("\n");
    }
        
    
    return 0;
}
  • 对上一步进行优化,上一步慢就慢在每个毯子内的所有点都要枚举,然后加1作为此点被一个毯子覆盖的标记,所以我们需要二维差分对这一步进行优化。
  • 不懂二维差分的请移步:https://www.cnblogs.com/Roni-i/p/9354177.html
  • 这样子,我们通过差分就少枚举了很多点,时间复杂度最差降为O(m*n+n2)。
#include
#include
#include
using namespace std;
const int maxn = 1000 + 5;
int n,m;
int ans[maxn][maxn],p[maxn][maxn];

int main(void){
    scanf("%d%d",&n,&m);
    memset(ans,0,sizeof(ans));
    memset(p,0,sizeof(p));
    int x1,y1,x2,y2;
    for(int i = 0;i < m;++i){
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        for(int row = y1;row <= y2;row++){
            p[x1][row]++; p[x2 + 1][row]--; //注意这边是x2+1
        }
    }
     
    for(int x = 1;x <= n;++x){
        for(int y = 1;y <= n;++y){
            ans[x][y] = ans[x-1][y] + p[x][y];
            if(y == 1) printf("%d",ans[x][y]);
            else printf(" %d",ans[x][y]);
        }
        printf("\n");
    }
    return 0;
}

再来一道前缀和/差分:洛谷P3406

  • 读完题大概就知道要统计出每段铁路经过次数,然后贪心求解:要么全选票,要么全时买卡后充值。因为n和m为105,很明显要用差分的思想,然后求前缀和(因为用了差分,所以求每段铁路的访问次数时要求前缀和)。
  • 为什么可以贪心:假设最优解中两种方式都选择了,但是题目说bi
  • 还有要注意,因为打算求前缀和,所以当后面访问的城市在前面访问的城市的前面的时候(返回乘车),仍然需要给地图上后面城市的那段铁路做上差分标记,所以需要分类讨论下位置
  • 这题所有都要开longlong,除了m和n,因为费用可能很高。

AC代码:

#include
#include
#include
#include
#define LL long long
using namespace std;
const int maxn = 1e5 + 5;
LL a[maxn],b[maxn],c[maxn],times[maxn],city[maxn];
int n,m;

int main(void){
    memset(times,0,sizeof(times));
    scanf("%d%d",&n,&m);
    for(int i = 1;i <= m;++i) cin >> city[i];
    for(int i = 1;i <= n - 1;++i)  cin >> a[i] >> b[i] >> c[i];
    
    //次数
    for(int i = 2;i <= m;++i){
        if(city[i] < city[i-1]){
            times[city[i-1]]--; times[city[i]]++;
        }
        else if(city[i] > city[i-1]){
            times[city[i]]--; times[city[i-1]]++;
        }
    }
    
    LL ans = 0;
    for(int i = 1;i < n;++i){
        times[i] += times[i-1]; ans += min(times[i]*a[i],c[i] + b[i] * times[i]);
    } 
    cout << ans;
    return 0;
}

你可能感兴趣的:(Prefix)