洛谷 P1438 无聊的数列 题解

题目链接:洛谷P1438 无聊的数列


哈喽,大家好,我们又见面啦!

今天我们来看 洛谷P1438 无聊的数列。

看到此题,我们首先考虑的就是暴力、树状数组和线段树(本题解并未讲解线段树)。

首先我们可以尝试暴力

暴力 得分:91 分(还不错,开 O2 + 快读 + C++98)

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int read(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*f;
}
const int N=1e6+10;
int n=read(),m=read(),op,x,y,z,w,a[N];
signed main(){
    for(int i=1;i<=n;i++){
        a[i]=read();
    }
    while(m--){
        op=read();
        x=read();
        if(op==1){
            y=read();
            z=read();
            w=read();
            for(int i=x;i<=y;i++){
                a[i]+=z;
                z+=w;
            }
        }
        else{
            printf("%d\n",a[x]);
        }
    }
	return 0;
}

所以,此题我们不能单纯用暴力,我们要用树状数组!


首先,我们考虑一种比较慢点方法,循环单点修改。

代码很简单,得分:27 分(还没暴力快)。

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
#define lowbit(x) x&(-x)
using namespace std;
const int N=1e7+10;
int n,m,op,x,y,z,w,t,tr[N];
void upd(int i,int x){
    while(i<=n){
        tr[i]+=x;
        i+=lowbit(i);
    }
}
int getsum(int x){
    int ans=0;
    while(x>0){
        ans+=tr[x];
        x-=lowbit(x);
    }
    return ans;
}
signed main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>x;
        upd(i,x);
    }
    while(m--){
        cin>>op>>x;
        if(op==1){
            cin>>y>>z>>w;
            for(int i=x;i<=y;i++){
                upd(i,z);
                z+=w;
            }
        }
        else{
            cout<<getsum(x)-getsum(x-1)<<"\n";
        }
    }
    return 0;
}

于是,现在开始讲正解:

我们用树状数组 + 二阶差分。

下面是推导过程:

首先考虑公差为 0 的情况:

这种情况比较简单,我们可得:

然后,我们就可以得到:

还有一种情况,首项为 0 。相似,我们最终得到:

于是,结果就出来了:

推出式子以后,代码就简单了。

100分代码(最慢点 118 ms,还不错):

cpp 复制代码
#include<bits/stdc++.h>
#define int long long
#define lowbit(x) x&(-x)
using namespace std;
const int N=1e7+10;
int n,m,op,x,y,z,w,a[N],b[N],tr[N],tr1[N];
void upd(int x,int y){
	int tmp=x;
	while(x<=n){
		tr[x]+=y;
		tr1[x]+=y*tmp;
		x+=lowbit(x);
	}
}
int getsum(int x){
	int tmp=x,ans=0;
	while(x){
		ans+=(tmp+1)*tr[x]-tr1[x];
		x-=lowbit(x);
	}
	return ans;
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		b[i]=a[i]-a[i-1];
		upd(i,b[i]-b[i-1]);
	}
	while(m--){
		cin>>op;
		if(op==1){
			cin>>x>>y>>z>>w;
			upd(x,z);
			upd(x+1,w-z);
			upd(y+1,-z-(y-x+1)*w);
			upd(y+2,z+(y-x)*w);
		}
		else{
			cin>>x;
			cout<<getsum(x)<<"\n";
		}
	}
	return 0;
}

最后,我们略微看一下线段树的解法:

100 分代码如下(最慢点81 ms,挺快的):

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
long long tr[N*4],tag[N*4],tag1[N*4];
void pu(long long p,long long l,long long r){
    if(tag[p]||tag1[p]){
        long long mid=(l+r)>>1;
        tr[p<<1]+=tag[p]*(mid-l+1)+tag1[p]*(mid-l+1)*(mid-l)/2;
        tag[p<<1]+=tag[p];
        tag1[p<<1]+=tag1[p];
        long long k1=tag[p]+tag1[p]*(mid-l+1);
        tr[p<<1|1]+=k1*(r-mid)+tag1[p]*(r-mid)*(r-mid-1)/2;
        tag[p<<1|1]+=k1;
        tag1[p<<1|1]+=tag1[p];
        tag[p]=tag1[p]=0;
    }
}
void upd(long long t,long long l,long long r,long long l1,long long r1,long long k,long long d){
    if(l1<=l&&r<=r1){
        long long cnt=r-l+1;
        long long tt=k+d*(l-l1);
        tr[t]+=tt*cnt + d*cnt*(cnt-1)/2;
        tag[t]+=tt;
        tag1[t]+=d;
        return;
    }
    pu(t,l,r);
    long long mid=(l+r)>>1;
    if(l1<=mid) upd(t<<1,l,mid,l1,r1,k,d);
    if(r1>mid) upd(t<<1|1,mid+1,r,l1,r1,k,d);
    tr[t]=tr[t<<1]+tr[t<<1|1];
}
long long qu(long long t,long long l,long long r,long long tmp){
    if(l==r) return tr[t];
    pu(t,l,r);
    long long mid=(l+r)>>1;
    if(tmp<=mid) return qu(t<<1,l,mid,tmp);
    else return qu(t<<1|1,mid+1,r,tmp);
}
long long n,m,a,ttt,l,r,k,d;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n>>m;
    for(long long i=1;i<=n;i++){
        cin>>ttt;
        upd(1,1,n,i,i,ttt,0);
    }
    while(m--){
        cin>>a;
        if(a==1){
            cin>>l>>r>>k>>d;
            upd(1,1,n,l,r,k,d);
        }
        else{
            cin>>l;
            cout<<qu(1,1,n,l)<<'\n';
        }
    }
    return 0;
}

于是,我们就做完了这道题。


后记: 推导过程部分 学习洛谷 ll_dio题解,在此鸣谢。

感谢大家阅读,我们下次再见(看在作者努力修改格式的面上,能不能给个关注啊)。

相关推荐
CoderYanger20 小时前
递归、搜索与回溯-综合练习:19.目标和
java·算法·leetcode·1024程序员节
mit6.82420 小时前
dfs|mask^翻转
算法
SKYDROID云卓小助手20 小时前
三轴云台之控制协同技术
服务器·网络·图像处理·人工智能·算法
Zx6236520 小时前
13.泛型编程 STL技术
java·开发语言·c++
The Last.H20 小时前
Educational Codeforces Round 185 (Rated for Div. 2)A-C
c语言·c++·算法
caron421 小时前
C++ 推箱子游戏
开发语言·c++·游戏
fengfuyao98521 小时前
匈牙利算法的MATLAB实现
java·算法·matlab
路过君_P21 小时前
C++ 算法题解:迷宫寻路
c++·算法·深度优先
止观止21 小时前
告别“祖传C++”:开启你的现代C++之旅
c++·c++11·c++20·编程思想·现代c++