前缀和与递推

前缀和与递推及练习题

前导知识

参考如下博客:

前缀和与差分 图文并茂 超级详细整理(全网最通俗易懂)

1. 10471:数列游戏 I(差分数组+前缀和)

ZZUOJ 10471

#include <iostream>
using namespace std;
#define int long long
const int mod = 1000000007;
const int N = 1000005;
int aa[N],la[N],ra[N],c[N],lb[N],rb[N];
signed main(){
    int t;
    cin>>t;
    while(t--){
        int n,a,b;
        cin>>n>>a>>b;
        for(int i=1;i<=n;i++){
            aa[i]=0;
        }
        aa[0]=0;
        for(int i=1;i<=a;i++){
            cin>>la[i]>>ra[i]>>c[i];
            for(int j=la[i];j<=ra[i];j++){
                aa[j]+=c[i] % mod;
            }
        }
        for(int j=1;j<=n;j++){
            aa[j]+=aa[j-1] % mod;
        }
        for(int i=1;i<=b;i++){
            cin>>lb[i]>>rb[i];
            cout<<aa[rb[i]] % mod - aa[lb[i]-1] % mod<<endl;
        }
    }
    return 0;
}

差分数列,如有个数组a, 差分数列为d, d[i]=a[i]-a[i-1],因此改变某段区间的值时改变首尾元素的值即可。

2. 扫雷游戏简化版(递推+动态规划)

传送门

a [ i − 1 ] = d p [ i ] + d p [ i − 1 ] + d p [ i − 2 ] a[i-1]=dp[i]+dp[i-1]+dp[i-2] a[i1]=dp[i]+dp[i1]+dp[i2]

其中 d p dp dp为第一列i号位置有几颗雷, a a a为第二列第 i i i​号位置的数字.

#include <cstdio>
using namespace std;
const int N = 100005;
int a[N],dp[N],n;
bool check(){
    for(int i=2;i<=n;i++){
        dp[i]=a[i-1]-dp[i-1]-dp[i-2];
        if(dp[i]<0)
            return false;
    }
    return dp[n-1]+dp[n]==a[n];
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    int t=0;
    if(check()){
        t++;
    }
    dp[1]=1;    /* 重新开始第二种情况 第一个位置有雷 */
    if(check())
        t++;
    printf("%d\n",t);
    return 0;
}

3. 扫雷游戏(枚举+搜索)

传送门

#include <iostream>
#include <cstdio>
using namespace std;
const int dx[8] = {1,-1,1,1,-1,-1,0,0},
          dy[8] = {0,0,1,-1,1,-1,-1,1};/* 八个方向 */
int n,m;
char a[110][110];
bool isValid(int x,int y)
{
    if (x >= 1 && x <= n && y >= 1 && y <= m && a[x][y] == '*')/* 判断当前搜索是否越界,且当前枚举点为“?” */
        return true;
    return false;
}
int Search(int x,int y)/* 按照八个方向搜索 */
{
    int ans = 0;
    for (int i=0;i<8;++i)
        {
            int new_x= x + dx[i];
            int new_y= y + dy[i];
            if (isValid(new_x,new_y))
                ans++;/* 累加 */
        }
    return ans;/* 返回答案 */
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
         for (int j=1;j<=m;++j)
               cin >> a[i][j];
    /* 读入 */
    for (int i=1;i<=n;++i)
        for (int j=1;j<=m;++j)
            {
               if (a[i][j] == '*')
                    cout << '*';
                if (a[i][j] == '?')
                    cout << Search(i,j);/* 输出 */
                if (j== m)
                    cout << endl;/* 换行 */
            }
    return 0;
}

4. 因数和(递推+线性筛)

传送门

要记住的点:

  • 因子和 σ ( n ) = ∑ d ∣ n d σ(n)=\sum_{d|n} d σ(n)=dnd
  • x ( m o d   y ) = x − [ x y ] × y x (mod\ y)=x-[\frac{x}{y}] \times y x(mod y)=x[yx]×y

预处理 σ ( i ) σ(i) σ(i)

每个因数的对哪些数有贡献 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

// 暴力筛O(nlogn)
ll f[N],s;
int n;
void init(int n){
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j+=i)
			f[j]+=i;
	}
}

公式推导原理
f ( 1 ) = 1 ; f ( 2 ) = 1 + 2 ; f ( 3 ) = 1 + 3 ; f ( 4 ) = 1 + 2 + 4 ; f ( 5 ) = 1 + 5 ; . . . f(1)=1;\\f(2)=1+2;\\f(3)=1+3;\\f(4)=1+2+4;\\f(5)=1+5;\\... f(1)=1;f(2)=1+2;f(3)=1+3;f(4)=1+2+4;f(5)=1+5;...

线性筛 时间复杂度: O ( n ) O(n) O(n)

//线筛O(n)
ll f[N],s,g[N];
int p[N],cnt,vis[N];
int n;
void init(int n){
	vis[0]=vis[1]=1;
	f[1]=1;
	for(int i=2;i<=n;i++){
		if(!vis[i]) p[++cnt]=i,f[i]=g[i]=i+1;
		for(int j=1;j<=cnt&&i*p[j]<=n;j++){
			vis[i*p[j]]=1;
			if(i%p[j]==0){
				g[i*p[j]]=g[i]*p[j]+1;
				f[i*p[j]]=f[i]/g[i]*g[i*p[j]];
				break;
			}
			f[i*p[j]]=f[i]*f[p[j]];
			g[i*p[j]]=g[p[j]];
		}
	}
}

f [ i ] f[i] f[i]即为所求 σ ( i ) σ(i) σ(i)

完整程序

#include <cstdio>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
/* 暴力筛O(nlogn) */
/*
ll f[N],s;
int n;
void init(int n){
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j+=i)
			f[j]+=i;
	}
}
*/
/* 线筛O(n) */
ll ans[N];
ll f[N],s,g[N];
ll p[N],cnt,vis[N];
ll n;
void init(ll n){
    vis[0]=vis[1]=1;
    f[1]=1;
    for(int i=2;i<=n;i++){
        if(!vis[i]) p[++cnt]=i,f[i]=g[i]=i+1;
        for(int j=1;j<=cnt&&i*p[j]<=n;j++){
            vis[i*p[j]]=1;
            if(i%p[j]==0){
                g[i*p[j]]=g[i]*p[j]+1;
                f[i*p[j]]=f[i]/g[i]*g[i*p[j]];
                break;
            }
            f[i*p[j]]=f[i]*f[p[j]];
            g[i*p[j]]=g[p[j]];
        }
    }
}
int main(){
    scanf("%lld",&n);
    init(n);
    for(int i=1;i<=n;i++){
        ans[i]=ans[i-1]+n-f[i];
        printf("%lld ",ans[i]);
    }
    return 0;
}

5. 牛妹吃豆子(二维前缀和)

传送门

#include <cstdio>
using namespace std;
typedef long long ll;
const int N=2e3+5;
ll f[N][N]={0};
int main()
{
    int n,m,k,q,x1,y1,x2,y2;
    scanf("%d%d%d%d",&n,&m,&k,&q);
    while(k--){
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        f[x1][y1]++;
        f[x2+1][y2+1]++;
        f[x2+1][y1]--;
        f[x1][y2+1]--;
    }
    /* 做差分 */
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++)
            f[i][j]+=f[i-1][j]+f[i][j-1]-f[i-1][j-1];
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++)
            f[i][j]+=f[i-1][j]+f[i][j-1]-f[i-1][j-1];
    }
    while(q--){
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        printf("%lld\n",f[x1-1][y1-1]+f[x2][y2]-f[x1-1][y2]-f[x2][y1-1]);
    }
    return 0;
}

注意数组应开一个long long 型的,保证输出数据量

6. 数楼梯(高精度+斐波那契数列)

传送门

#include <cstdio>
using namespace std;
const int N = 5005;
int n;
int f[N][N];
int l=1;
void add(int k){
    /* 高精度加法,k来存阶数 */
    for(int i=1;i<=l;i++){
        f[k][i]=f[k-1][i]+f[k-2][i];    /* 斐波那契公式计算 */
    }
    for(int i=1;i<=l;i++){
        if(f[k][i]>=10){
            f[k][i+1]+=f[k][i]/10;  /* 进位,保证存储更多的数据 */
            f[k][i]=f[k][i]%10;
            if(f[k][l+1]){  /* 长度增加,如果下一位不为0 */
                l++;
            }
        }
    }
}
int main(){
    scanf("%d",&n);
    f[1][1]=1;
    f[2][1]=2;
    for(int i=3;i<=n;i++){
        add(i);
    }
    for(int i=l;i>=1;i--)
        printf("%d",f[n][i]);
    return 0;
}

7. 最大正方形(前缀和+动态规划)

传送门

题解

( i , j ) (i,j) (i,j)为右下角结点,当 a [ i ] [ j ] = = 1 a[i][j]==1 a[i][j]==1时,检查左、上、左上角的结点是否为0,以 f [ i ] [ j ] f[i][j] f[i][j]来表示以 ( i , j ) (i,j) (i,j)​为结点的最大正方形的边长

dp做法

#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 105;
int a[N][N];
int dp[N][N];
int n,m;
int ans=0;
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%d",&a[i][j]);
            if(a[i][j]==1){
                dp[i][j]=min(min(dp[i][j-1],dp[i-1][j]),dp[i-1][j-1])+1;
            }
            ans=max(ans,dp[i][j]);
        }
    }
    printf("%d",ans);
    return 0;
}

二维前缀和+二分解释

#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 105;
int a[N][N];
int n,m;
int ans=0;
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%d",&a[i][j]);
            a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            int l=0,r=min(n,m);
            while(l<=r){	/* 引入二分节省时间 */
                int mid=(l+r)>>1;
                if(i+mid>n||j+mid>m||a[i+mid][j+mid]-a[i+mid][j]-a[i][j+mid]+a[i][j]<mid*mid)
                    r=mid-1;
                else
                    l=mid+1;
            }
            if(a[i+r][j+r]-a[i+r][j]-a[i][j+r]+a[i][j]==r*r){
                ans=max(ans,r);
            }
        }
    }
    printf("%d",ans);
    return 0;
}

8. 领地选择(二维前缀和)

传送门

#include <cstdio>
#include <algorithm>
using namespace std;
#define int long long
const int N = 3e3;
int n,m,c;
int a[N][N];
signed main(){
    scanf("%lld%lld%lld",&n,&m,&c);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%lld",&a[i][j]);
            a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
        }
    }
    int ans=-0x7fffffff;
    int ii=1,jj=1;
    for(int i=c;i<=n;i++){
        for(int j=c;j<=m;j++){
            int t=a[i][j]-a[i-c][j]-a[i][j-c]+a[i-c][j-c];
            if(t>ans){
                ii=i-c+1;
                jj=j-c+1;
                ans=t;
            }
        }
    }
    printf("%lld %lld",ii,jj);
    return 0;
}

9. Foehn Phenomena(差分)

传送门

差分板子题

#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
ll a[4000005];
ll n,q,s,t;
/* 速读 */
inline ll read(){
    char c=getchar();
    int f=1;
    ll x=0;
    while(c<'0'||c>'9'){
        if(c=='-')
            f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+(c^'0');
        c=getchar();
    }
    return x*f;
}
/* 温度变化计算 */
ll temp(ll x){
    if(x>=0)
        return -x*s;
    else return -x*t;
}
int main(){
    ll ans=0;
    n=read();q=read();s=read();t=read();
    ll last=0;
    for(register int i=0;i<=n;i++){
        ll k=read();
        a[i]=k-last;
        ans+=temp(a[i]);
        last=k;
    }
    /* 差分 */
    for(register int i=1;i<=q;i++){
        ll l=read(),r=read(),k=read();
        ans-=temp(a[l]),a[l]+=k,ans+=temp(a[l]);
        /* 借用数学思想的处理方法 */
        if(r!=n) ans-=temp(a[r+1]),a[r+1]-=k,ans+=temp(a[r+1]);
        printf("%lld\n",ans);
    }
    return 0;
}

这个register int中的register表示使用cpu内部寄存器(寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件)的变量,而平时的int是把变量放在内存中,存到寄存器中可以加快变量的读写速度

速读

inline ll read(){
    char c=getchar();
    int f=1;
    ll x=0;
    while(c<'0'||c>'9'){
        if(c=='-')
            f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=(x<<1)+(x<<3)+(c^'0');
        c=getchar();
    }
    return x*f;
}

10. 无聊的数列(线段树+差分)

传送门

没有算法本质的做法

#include <cstdio>
#define int long long
const int N = 1e5 + 5;
int n,m;
int a[N];
int opt;
int l,r,K,D;
int p;
signed main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    while(m--){
        scanf("%d",&opt);
        if(opt==1){
            scanf("%d%d%d%d",&l,&r,&K,&D);
            for(int i=l;i<=r;i++){
                a[i]+=K+(i-l)*D;
            }
        }else if(opt==2){
            scanf("%d",&p);
            printf("%d\n",a[p]);
        }
    }
    return 0;
}

线段树做法

#include<iostream>
#include<cstdio>
#define ls root<<1,l,mid
#define rs root<<1|1,mid+1,r
#define MAXN 100001
using namespace std;
int n,m;
int s[MAXN],sum[MAXN<<2],lazy[MAXN<<2];

inline void push_up(int root)
{
    sum[root]=sum[root<<1]+sum[root<<1|1];
}

inline void push_down(int root,int len)
{
    if(lazy[root])
    {
        lazy[root<<1]+=lazy[root];
        lazy[root<<1|1]+=lazy[root];
        sum[root<<1]+=(len-(len>>1))*lazy[root];
        sum[root<<1|1]+=(len>>1)*lazy[root];
        lazy[root]=0;
    }
}

inline void update(int root,int l,int r,int L,int R,int val)
{
    if(L<=l && r<=R){sum[root]+=(r-l+1)*val;lazy[root]+=val;return;}
    push_down(root,r-l+1);
    int mid=(l+r)>>1;
    if(mid>=L) update(ls,L,R,val);
    if(mid<R) update(rs,L,R,val);
    push_up(root);
}

inline int query(int root,int l,int r,int L,int R)
{
    if(L<=l && r<=R) return sum[root];
    push_down(root,r-l+1);
    int mid=(l+r)>>1;int total=0;
    if(mid>=L) total+=query(ls,L,R);
    if(mid<R) total+=query(rs,L,R);
    return total;
}

int main()
{
    in(n),in(m);
    for(int i=1;i<=n;i++) in(s[i]);
    int type,L,R,K,D,ask;
    while(m--)
    {
        in(type);
        if(type==1)
        {
            in(L),in(R),in(K),in(D);
            update(1,1,n,L,L,K);
            if(R>L) update(1,1,n,L+1,R,D);
            int N=R-L+1;
            if(R!=n) update(1,1,n,R+1,R+1,-(K+(N-1)*D));
        }
        else
        {
            in(ask);
            printf("%d\n",s[ask]+query(1,1,n,1,ask));
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DeeGLMath

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值