小阳的贝壳 题解
小阳的贝壳
https://ac.nowcoder.com/acm/problem/26255
前言
在错了N次之后,改动了一个小地方,多了一个+1就过了这一题,太惨了。
思路
假设只有前两个操作,我们实际上只需要生成一个差分数组,维护一个单点修改以及区间查询最大值即可。
但是最难的是操作3,区间GCD。
小蒟蒻在这里卡了许久,冥思苦想还是没有想到办法,最后百度:差分数组 与 区间GCD
然后知道了这个柿子:
也就是:
于是整个题目的基本做法已经出来了:
维护一个差分数组的绝对值最大值以及绝对值的gcd,然后对于每次操作1,就相当于是进行了一次单点修改,然后要维护区间信息,剩余的两个就是区间查询.gcd是具有区间可维护性的,也就是gcd(a,b,c,d) = gcd(gcd(a,b),gcd(c,d))
于是可以使用线段树。
我的做法是:
开两棵线段树,一棵维护差分数组,一棵维护原数组(操作三需要知道 是多少)。
第一棵线段树存的是
第二棵是:
对于操作一,在线段树一进行单点修改,线段树二中进行区间修改。
对于操作二,直接在线段树一中进行区间查询 的最大值(因为是差分数组,所以维护的是,因此查询的左端点要+1!就是这里导致我Wa了n次)
对于操作三,调用第二棵线段树求出,然后再调用线段树查询 区间 差分绝对值数组的gcd,然后再对得到的这两个值进行gcd查询。
更优的做法是:
不难看出我是个shabi,居然开了两棵线段树
假定得到的差分数组为 ,
那么实际上
开一棵线段树即可。维护区间GCD,区间差分数组最大值,区间和
这个做法好多了,好写又好调。
最后放上小蒟蒻丑丑的代码:
Code
#include <bits/stdc++.h> using namespace std; #define int long long const int MAXN = 1e5 + 50; int data[MAXN],n,Q;//data[]表示给定数组 int cha[MAXN];//差分数组 struct SegmentTree { int Max,l,r,G,v,laz,sum; } T[MAXN * 4]; inline int read() { int x = 0 , flag = 1; char ch = getchar(); for( ; ch > '9' || ch < '0' ; ch = getchar()) if(ch == '-') flag = -1; for( ; ch >= '0' && ch <= '9' ; ch = getchar()) x = (x << 3) + (x << 1) + ch - '0'; return x * flag; } void build(int x,int l,int r)//建树的时候我的线段树一和线段树二是一起建的,并且用的同一个结构体 { T[x].l = l , T[x].r = r; if(l == r){T[x].Max = T[x].G = abs(cha[l]) ; T[x].v = cha[l] , T[x].sum = data[l]; return ; } int mid = (l + r) >> 1ll; build(x << 1 , l , mid); build(x << 1 | 1 , mid + 1 , r); T[x].Max = max(T[x << 1].Max , T[x << 1 | 1].Max); T[x].G = __gcd(T[x << 1].G, T[x << 1 | 1].G); return ; } //下面是第一棵线段树 ------ start ------- void change(int x,int pos,int k) //单点修改信息 { if(pos > n || pos < 0) return ; if(T[x].l == pos && T[x].r == pos){ T[x].v += k; T[x].Max = abs(T[x].v); T[x].G = abs(T[x].v); return ; } int mid = (T[x].l + T[x].r) >> 1; if(pos <= mid) change(x << 1 , pos , k); else change(x << 1 | 1 , pos, k); T[x].Max = max(T[x << 1].Max , T[x << 1 | 1].Max); T[x].G = __gcd(T[x << 1].G, T[x << 1 | 1].G); return ; } int GetMax(int x,int l,int r)//获得差分数组区间最大值 { int Max = -0x3f; if(T[x].l >= l && T[x].r <= r) return T[x].Max; int mid = (T[x].l + T[x].r) >> 1; if(l <= mid) Max = max(Max,GetMax(x << 1 , l ,r)); if(r > mid) Max = max(Max,GetMax(x << 1 | 1 , l ,r)); return Max; } int GetGCD(int x,int l,int r)//获得区间gcd { int G = 0; if(T[x].l >= l && T[x].r <= r) return T[x].G; int mid = (T[x].l + T[x].r) >> 1; if(l <= mid) G = __gcd(G,GetGCD(x << 1 , l , r)); if(r > mid) G = __gcd(G,GetGCD(x << 1 | 1 , l , r)); return G; } //线段树1 ------ end ------ //下面是第二棵线段树 ------- start -------- void ad(int x,int k) { T[x].laz += k; T[x].sum = T[x].sum + k; return ; } void pushdown(int x)//懒标记下传 { if(T[x].laz == 0ll) return ; ad(x << 1 , T[x].laz); ad(x << 1 | 1 , T[x].laz); T[x].laz = 0; return ; } int Get(int x,int pos)//获得单点值 { if(T[x].l == T[x].r && T[x].l == pos) return T[x].sum; pushdown(x); int mid = (T[x].l + T[x].r) >> 1; if(pos <= mid) return Get(x << 1 , pos); else return Get(x << 1 | 1 , pos); } void changesum(int x,int l,int r,int k) { if(T[x].l >= l && T[x].r <= r) {ad(x,k) ; return ;}; pushdown(x); int mid = (T[x].l + T[x].r) >> 1; if(l <= mid) changesum(x << 1 , l , r , k); if(r > mid) changesum(x << 1 | 1 , l , r , k); return ; } // 第二棵线段树 ------- end -------- signed main() { n = read() , Q = read(); for(int i = 1 ; i <= n ; i ++) data[i] = read(),cha[i] = data[i] - data[i - 1]; cha[1] = 0;//实际上我的做法用不到差分数组第一个数,赋值成0 build(1 , 1 , n); while(Q --) { int op = read(),l = read() , r = read() , x; if(op == 1) x = read(); if(op == 1) { change(1,l, x); change(1,r + 1 , -x);//单点修改 changesum(1,l,r,x);//区间修改 continue; } if(op == 2) { if(l == r) {cout << 0 << endl; continue;} cout << GetMax(1,l + 1,r) << endl;//!!!这里注意一定要+1,血淋淋的教训! //因为是差分数组,所以维护的是a[i] - a[i - 1],题目求的是[l,r],如果不加+1,就求成[l - 1 , r] 了。 } else { if(l == r) {cout << Get(1,l) << endl; continue;}//特判一下 cout << __gcd(Get(1,l),GetGCD(1,l + 1 , r)) << endl; } } return 0; }
闲人碎语 文章被收录于专栏
刊载算法学习笔记以及一些题解