首页 > 其他分享 >题解 SP15454

题解 SP15454

时间:2023-08-03 10:12:48浏览次数:42  
标签:return int 题解 SP15454 text 区间 inline 我们

前言

数学符号约定

\(\operatorname{lowbit}(x)\):表示 \(x\) 的二进制最低位。

\([a,b]\):表示区间 \(a\sim b\),其中包含 \(a,\,b\) 端点,其区间长度为 \(b - a + 1\)。

如非特殊说明,将会按照上述约定书写符号。

题目大意

有一个初始全为 \(0\) 的序列 \(A\),你需要支持一下操作:

add P F:将 \(a_P\) 增加 \(F\)。

find L R:查询区间 \([L,R]\) 的和。

题目分析

树状数组板子题,因为是模板题,所以在这里介绍一下树状数组。

很多时候,大家习惯性的把树状数组放在线段树前面讲,然而我认为把线段树放在树状数组前面讲更够更方便大家理解树状数组这个数据结构。

如果你熟悉线段树的话,看到上图应该就能立刻理解树状数组是个什么东西了,它本质上就是一颗没有右儿子的线段树。

我们首先考虑建立一棵树,让叶子节点维护原始信息,非叶子节点维护区间信息。然后我们让这棵树成为一颗二叉树,因为这样的复杂度可以跟二分一样挂钩。然后让一个节点左儿子维护的是区间 \([L,mid]\),让右儿子维护 \([mid+1,R]\),这样一颗二叉树就是我们所说的线段树,它的建树是 \(O(n)\) 的,单点修改和区间查询都是 \(O(\log n)\) 的。

但是,让我们考虑一下,我们真的需要把整棵线段树全部建出来吗?答案是否定的,在本题中,像这种「单点加、维护区间和」问题,我们可以偷个懒,我们把线段树的右儿子全部砍掉,然后考虑此时我们的单点修改,其实就可以直接对该点所在的区间进行增量变化。

这么说可能比较抽象,我们来一起举个例子:现在,我们需要修改 \(a_4\) 这个位置,如果是线段树的话,我们就需要递归到维护 \(a_4\) 的叶子节点,增加完后在一步一步的 pushup 上去,但是这样太麻烦了,我们换一个思路,我们修改的时候从离 \(4\) 最近的左儿子——区间 \(1 \sim 4\) 开始改,然后一步一步往上跳直到跳到根节点,边跳边改,这样就没有我们递归下去的那一部分常数了。

但是,我们的任何创新都要建立在正确的基础上,我们先反问一下自己,这样能保证正确性吗?如果你要是还像线段树那样区间查询的话,那确实是不能保证正确性的。因为毕竟我们砍掉了右儿子嘛,我们递归到的区间拼起来肯定是不完整的。不过,让我们换个思路,考虑转换一下区间和,不难想到,区间和还等于两个前缀和的差。所以如果我们能够保证查询前缀和的结果是正确的就好了。

幸运的是,我们可以保证查询前缀和的正确性,我们考虑我们离某数 \(x\) 最近的左儿子所维护的区间右端点,实际上就是数 \(x\) 本身,那么前缀和一定保证需要加上 \(x\) 及其之前的部分,所以此时我们可以保证查询的前缀和一定是完整的。其实,上面内容所讲的数据结构,就是现在我们要讲的「树状数组」。

好,原理讲完了,现在该谈谈如何写代码,因为我们将 \(a_x\) 的信息放到了离 \(x\) 最近的左儿子里面,然后它还是那个左儿子所维护的区间的右端点,所以我们不妨直接让我们的数组 \(B\) 的第 \(x\) 位置维护那个左儿子所维护的区间。然后我们现在对编号进行一下分析,不难发现,我们的父亲节点一定在 \(x + \operatorname{lowbit}(x)\) 里面。证明比较复杂,不过理由如下:

首先,我们肯定有如下引理:

\(\text{Lemma 1}\):若当前点 \(x\) 表示的区间长度为 \(len\),那么它的父区间所代表的节点位置一定是 \(x + len\)。

\(\text{Proof}\):考虑我们子区间的定义是来自于向下分治的结果,所以子区间的长度一定是父区间的一半,由于我们砍掉了分治的右向分支,并用右端点 \(a\) 的位置保存最长的右端点为 \(a\) 的区间信息,所以不难得出,当我们 $x $ 增加 \(len\) 的时候就跳到了区间的右端点,也就是 \(x\) 所代表区间的父区间。

所以,我们现在所需要证明的就变成了 \(\operatorname{lowbit}(x)\) 为 \(x\) 所代表的区间。

首先,我们不难得出一个引理:

\(\text{Lemma 2}\):对于编号为 \(2^n\) 的位置,其一定代表了区间 \([1,2^n]\)。

\(\text{Proof}\):首先,我们让位置 \(1\) 只维护单点 \(1\) 的信息,然后根据 \(\text{Lemma 1}\),我们不难得出其父区间位置为 \(2\),其父区间的父区间位置为 \(4\),以此类推,其第 \(n\) 级父区间位置一定为 \(2^n\),由于我们是从 \(1\) 一路推上来的,又由于位置 \(a\) 所维护的是最长的右端点为 \(a\) 的区间信息,故可以得出 \(\text{Lemma 2}\)。

其次,我们还有一个引理:

\(\text{Lemma 3}\):树状数组为一个分形结构。

\(\text{Proof}\):显然,因为树状数组是反向分治的结果,所以对于任意一对左右儿子,我们都有左儿子的结构与右儿子的一样。

那么对于一个数 \(x\),我们可以根据 \(\text{Lemma 3}\) 来得到一个新的引理:

\(\text{Lemma 4}\):位置 \(x\) 总是可以被分形直到所维护的相对区间为 \([1,2^n]\)。

\(\text{Proof}\):首先,我们总能将任意正整数分解为若干个 \(2^i\) 相加,于是我们不妨设 \(2^m\) 为 \(x\) 的最高位,根据 \(\text{Lemma 1}\),我们可以得出 \(x\) 所在的区间一定在 \([2^m+1,2^{m+1}]\) 区间内。根据 \(\text{Lemma 3}\),由于左右儿子结构相同,我们不妨对区间重新标号,让 \(2^m+1\) 为当前区间的 \(1\) 号位置。重复上述过程,直到 \(x\) 为 \(2^n\) 次方。此时根据 \(\text{Lemma 2}\),我们不难得出 \(x\) 所维护的即为相对区间 \([1,2^n]\)。

现在,我们可以综上了:

\(\text{Lemma 5}\):位置 \(x\) 所维护的区间长度为 \(\operatorname{lowbit}(x)\)。

\(\text{Proof}\):我们重新观察 \(\text{Lemma 4}\) 的证明过程,不难发现我们每一次分形都是让右儿子的位置编号全部减去 \(2^m\),也就是减去 \(x\) 的最高位,然后最后剩下的 \(2^n\) 其实就是 \(x\) 的最低位,也就是 \(\operatorname{lowbit}(x)\),又根据 \(\text{Lemma 4}\) 可以得出,位置 \(x\) 所维护的相对区间是 \([1,\operatorname{lowbit}(x)]\),故位置 \(x\) 所维护的区间长度为 \(\operatorname{lowbit}(x)\)。

故此时,对于单点加操作 add(pos, val),我们不难写出如下代码:

void add(int pos, int val) {
	for(int i = pos; i <= n; i += lowbit(i)){
		c[i] += val;
    }
}

查询操作 query(l,r) 就变成了:

int ask(int pos){
	int ans = 0;
	for(int i = pos; i; i -= lowbit(i)){
		ans += c[i];
	}
	return c[i];
}
int query(int l, int r){
	return ask(r) - ask(l-1);
}

代码实现

// clang-format off
// There is No Misaka for Codeforces. /dk

#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(x) cerr << #x << ": " << (x) << endl;
#define debugf(fmt, args...) fprintf(stderr, fmt, ##args)
#else
#define debug(...) void(114514);
#define debugf(...) void(1919810);
#endif
namespace FastIO  // Powered By LgxTpre. <3 <3 <3
{
template <typename T = int>
inline T read() {
    T s = 0, w = 1; char c = getchar();
    while (!isdigit(c)) { if (c == '-') w = -1; c = getchar(); }
    while (isdigit(c)) s = (s << 1) + (s << 3) + (c ^ 48), c = getchar();
    return s * w;
}
template <typename T>
inline void read(T &s) {
    s = 0; int w = 1; char c = getchar();
    while (!isdigit(c)) { if (c == '-') w = -1; c = getchar(); }
    while (isdigit(c)) s = (s << 1) + (s << 3) + (c ^ 48), c = getchar();
    s = s * w;
}
template <typename T, typename... Args> inline void read(T &x, Args &...args) { read(x), read(args...); }
template <typename T>
inline void write(T x, char ch) {
    if (x < 0) x = -x, putchar('-');
    static char stk[25]; int top = 0;
    do { stk[top++] = x % 10 + '0', x /= 10; } while (x);
    while (top) putchar(stk[--top]);
    putchar(ch);
    return;
}
}  // namespace FastIO

struct ModTools {
#define int long long
    ModTools(int MODD = 1e9 + 7) : MOD(MODD) { }
    void ChangeMod(int MODD) { MOD = MODD; }
	inline void Madd(int &a, int b) { a = a + b >= MOD ? a + b - MOD : a + b; }
    inline void Msub(int &a, int b) { a = a - b < 0 ? a - b + MOD : a - b; }
    inline void Mmul(int &a, int b) { a = a * b % MOD; }
    inline int add(int a, int b) { return (a + b >= MOD) ? (a + b - MOD) : (a + b); }
	inline int mul(int a, int b) { return (a % MOD) * (b % MOD) % MOD; }
	inline int sub(int a, int b) { return (a - b < 0) ? (a - b + MOD) : (a - b); }
    template <typename... Args> inline int add(int a, int b, Args &...args) { return add(a, add(b, args...)); }
    template <typename... Args> inline int mul(int a, int b, Args &...args) { return mul(a, mul(b, args...)); }
    template <typename... Args> inline void Madd(int &a, int b, Args &...args) { return Madd(a, add(b, args...)); }
    template <typename... Args> inline void Mmul(int &a, int b, Args &...args) { return Mmul(a, mul(b, args...)); }
    inline int inv(int x) {
        int A = 0, B = 0;
        int d = exgcd(x, MOD, A, B);
        assert(d == 1);
        return sub(A, 0);
    }
    inline int fastpow(int base, int index) {
        int ans = 1;
        while (index) {
            if (index & 1) ans = mul(ans, base);
            base = mul(base, base);
            index >>= 1;
        }
        return ans;
    }
    inline int sqr(int x) { return mul(x, x); }
private:
    int exgcd(int A, int B, int &X, int &Y) {
        if (B == 0) {
            X = 1, Y = 0;
            return A;
        }
        int res = exgcd(B, A % B, X, Y);
        int temp = Y;
        Y = X - A / B * Y;
        X = temp;
        return res;
    }
    int MOD;
#undef int
};

namespace NumberTheory {
inline int gcd(int A, int B) {
    int C;
    while (B) C = B, B = A % B, A = C;
    return A;
}
int exgcd(int A, int B, int &X, int &Y) {
    if (B == 0) {
        X = 1, Y = 0;
        return A;
    }
    int res = exgcd(B, A % B, X, Y);
    int temp = Y;
    Y = X - A / B * Y;
    X = temp;
    return res;
}
}  // namespace NumberTheory
// clang-format on
namespace Larry76 {
#define int long long

using namespace FastIO;
using namespace NumberTheory;
const int MAX_SIZE = 1e6 + 10;
const int INF = 0x7f7f7f7f7f7f7f7fll;
const double eps = 1e-9;
struct pii {
    int x, y;
    inline bool operator < (const pii &oth) const{
    	if(x==oth.x)
    		return y < oth.y;
    	return x < oth.x;
	}
};
ModTools mt(1e9 + 7);
inline int sqr(int x) { return x * x; }
inline int random(int l, int r) {
	static mt19937_64 rnd(time(0));
	uniform_int_distribution<int> dist(l,r);
	return dist(rnd);
}

int fac[MAX_SIZE];
int ifac[MAX_SIZE];
void initfac(int n) {
	fac[0] = 1;
	for(int i=1;i<=n;i++) {
		fac[i] = mt.mul(fac[i-1],i);
	}
	ifac[n] = mt.inv(fac[n]);
	for(int i=n-1;i>=0;--i) {
		ifac[i] = mt.mul(ifac[i+1], i+1);
	}
}

inline int C(int n, int m) { // n choose m
	return mt.mul(fac[n],ifac[m],ifac[n-m]);
}

inline int A(int m, int n) { // A_n^m
	return mt.mul(fac[n],ifac[m]);
}

///////////// Start Coding //////////////

int n,q;

int tree[MAX_SIZE];

inline int lowbit(int x){
	return x & (-x);
}

void add(int pos, int val){
	for(int i = pos; i<=n; i+=lowbit(i)){
		tree[i] += val;
	}
}

int ask(int pos){
	int ans = 0;
	for(int i=pos;i;i-=lowbit(i)){
		ans += tree[i];
	}
	return ans;
}

int query(int l, int r){
	return ask(r) - ask(l-1);
}

int findstr() {
	char c = getchar();
	while (c != 'a' && c != 'f') c = getchar();
	return c == 'a' ? 1 : 2;
}

void main(){
	read(n,q);
	while(q--){
		switch(findstr()){
			case 1:{
				int p = read();
				int f = read();
				add(p,f);
				break;
			}
			case 2:{
				int l = read();
				int r = read();
				write(query(l,r),'\n');
				break;
			}
		}
	}
}

////////////// End Coding ///////////////
#undef int
}  // namespace Larry76

int main() {
#ifdef LOCAL
    time_t t1 = clock();
#endif
    Larry76::main();
#ifdef LOCAL
    time_t t2 = clock();
    fprintf(stderr, "Used Time: %lldms.\n", t2 - t1);
#endif
    return 0 ^ 0;
}

标签:return,int,题解,SP15454,text,区间,inline,我们
From: https://www.cnblogs.com/larry76/p/17602511.html

相关文章

  • 题解 ARC104F
    前言在这里首先感谢一下题解区的FZzzz,本人的题解思路主要是基于他并给出了自己的理解。如非特殊说明,本题解中的数学符号原则上与题目中一致。题目分析需要转化的喵喵题。我们需要把原问题转化成一个图论计数问题,然后剩下的就很好办了。好,首先让我们修改一下题目的要求,将不......
  • 题解 AGC054D
    前言因为本人尚菜,所以本篇文章没有什么数学符号,请大家放心食用。题目分析先吐槽一嘴,这个o表示(),这个x表示)(,十分形象。好,我们先观察原序列,容易得出第一条性质:ox的加入不会让我们不合法的序列变合法,相反,它会让我们合法的序列变不合法。于是可以得出,无论如何,只要我们......
  • 题解 P9406【[POI2020-2021R3] Nawiasowania】
    一个显然的思路是:在排列\(p\)的括号串合法的基础上,使得左括号在原括号串中尽量靠左,这样答案更有可能合法。于是我们求出这个原括号尽量靠左的括号串(下文称为“最优括号串”),然后check合法性即可。下文中\(s\)是排列\(p\)的括号串。当\(n=2\)时,唯一的填法是令\(s_1\get......
  • 题解 P9326
    前言数学符号约定\(n\):任意正整数。\(\#\):从未出现过的小写字母。\(\Sigma\):字符集,这里指小写字母集合。\(S\):最终答案的字符矩阵。其余符号同题目翻译中所写。如非特殊说明,将会按照上述约定书写符号。题目大意构造一个\(N\timesM\)的小写字母矩阵,使得其中有\(R\)行......
  • 题解
    大力相应teacher要求。正难则反,考虑求不合法的三元组的数量。对于一个不合法的三元组,可以发现条件等价于三元组中有一个点出度为2。记\(m\)次操作后每个点出度为\(d_i\),答案就是\(\dbinom{n}{3}-\sum\limits_{i=1}^n\dbinom{d_i}{2}\)。那么怎么统计?回忆\(\mathcal{O}(......
  • Lua script attempted to access a non local key in a cluster node 问题解决
    一、问题描述最近优化公司需要对不同的业务系统的缓存工具提供一个标准化的解决方案。各个业务系统将缓存数据通过map结构进行存储,然后在缓存系统中将这些map获取出来,然后保存在redis数据库中。技术经理想到的最好解决方案是将map集合直接存储在redis的hash表中。但是要求对hash......
  • CF1468N 题解
    洛谷链接&CF链接题目简述共有\(T\)组数据,对于每组数据:有三个桶,五种垃圾,每个桶有固定的容量。前三种垃圾分别放入三种桶中,第四种垃圾可以放进\(1,3\)桶中,第五种垃圾可以放进\(2,3\)桶中。问题:对于给定的桶容量和垃圾量,请问垃圾是否可以全部放入桶中?思路简单贪心题。......
  • CF479C 题解
    洛谷链接&CF链接题目简述一个人想要安排期末考试的时间。有\(n\)场考试,每场考试有两个时间\(x_i,y_i\),一个是老师规定的时间,另外一个是他与老师商量好的考试时间。如果错过了,那就只能按照原来的考试时间考试。规定:只能按照原定考试的日期顺序进行考试的情况下,输出考完试......
  • 饭票 题解
    1.题意简述某天小\(x\)去食堂吃饭,手里有\(n\)种饭票,面值分别为\(A_1~A_n\),数量分别为\(C_1~C_n\)请你计算小\(x\)的饭票能组成多少在\([1,m]\)区间内的面值。2.样例解释3101242118样例中,我们有两张一元,一张两元和一张四元,可以凑出\(1\)到\(8\)元中所......
  • 【题解】Luogu[P2296] [NOIP2014 提高组] 寻找道路
    Link很简单的一道图论题。要在一个有向图上找一条\(s\)到\(t\)的最短路,要求这条路径上的所有点都满足:该点的所有出边所连点都能到达终点\(t\)。看上去很乱,我们简单分解一下,先在所有点中找到与终点有路径的点集\(A\)进行标记,再再所有点中找到其所有出边所连点都被打上标......