洛谷 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题解,在此鸣谢。

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

相关推荐
沙威玛_LHE9 小时前
树和二叉树
数据结构·算法
py有趣11 小时前
LeetCode算法学习之两数之和 II - 输入有序数组
学习·算法·leetcode
夏鹏今天学习了吗11 小时前
【LeetCode热题100(62/100)】搜索二维矩阵
算法·leetcode·矩阵
吃着火锅x唱着歌13 小时前
LeetCode 1128.等价多米诺骨牌对的数量
算法·leetcode·职场和发展
十八岁讨厌编程13 小时前
【算法训练营 · 补充】LeetCode Hot100(中)
算法·leetcode
橘颂TA14 小时前
【剑斩OFFER】算法的暴力美学——最小覆盖字串
算法·c/c++·就业
wearegogog12314 小时前
基于混合蛙跳算法和漏桶算法的无线传感器网络拥塞控制与分簇新方法
网络·算法
程序员龙一14 小时前
C++之static_cast关键字
开发语言·c++·static_cast
奶茶树14 小时前
【C++/STL】map和multimap的使用
开发语言·c++·stl
Tiandaren15 小时前
大模型应用03 || 函数调用 Function Calling || 概念、思想、流程
人工智能·算法·microsoft·数据分析