确实有意思
题目链接:link
题目大意
给你N个数,编号为1~n,支持以下2种操作:
1 a b k c: 对[a,b]内的满足(i - a) % k == 0,则使ai加上c(k很小,只有1到10)
2 a:求编号为a的数的值
解题思路
看到离散的数据头皮发麻,硬上肯定是不行的。尝试一些别的方法。
我们先化简一下式子(i-a)%k=0。
(i-a)%k=((i%k-a%k)+k)%k;考虑i%k和a%k的取值,均为0~k-1,意味着i%k-a%k的取值范围为-(k-1) ~ k-1。其中能够加上k在模k得到0的取值只有0。于是式子(i-a)%k=0可以化简为i%k=a%k。
从查询的角度来说,假设某一次查询编号i的ai,那么答案应该使ai的初始值加上ai变动的值。那么能让ai发生变动的修改就必须满足两个条件。一是对于某一个修改要求i%k=a%k,二是要求i要在[a,b]上。
注意到一点,对于一次查询来说只有i是现在的量,其他的k,a,a%k都是之前修改是涉及到的,也就是说现在我们是不知道的。那么这我们要怎么处理呢?
考虑让k从1到10循环,这样我们就可以知道i%k的值,我们设其为x。那么对于一个修改来说,如果a%k=x,我们就基本可以说i%k=a%k这个条件满足了。说是基本满足的原因是,我们现在循环的k不能保证和之前修改时候的k一样,也就不能保证这个i就是之前修改所作用的i。也就是说当这两个k相同且a%k=x的时候,i%k=a%k这个条件就被真正满足了。(后面做法中还会说明如何利用这个完成对数据结构的维护)
当把这个条件满足后,剩下的就是确定i是不是在[a,b]内,如果在就加上这次修改的取值,那么就比较容易想到使用树状数组区间维护单点查询。
做法
用bit[11][10][MAXN]构建55个树状数组(k是从1到10)
修改时对bit[k][a%k]所对应的树状数组进行区间修改。
查询时从1到n枚举k,对于每一个k,只有一个a%k与i%k对应,此时bit[k][num%k]对应的就是满足条件的树状数组,在这个树状数组中对num这个位置完成单点查询即可。
代码
#include<iostream>
#include<cstring>
using namespace std;
const int MAXN = 5e4 + 5;
struct BitTree
{
int bit[MAXN];//处理完模的问题之后你要记录的就是[l, r]内的i都要+=c
int lowbit(int x)
{
return x & (-x);
}
void update(int w, int num)
{
for (int i = w; i < MAXN; i += lowbit(i))
bit[i] += num;
}
int query(int w)
{
int ans = 0;
for (int i = w; i >= 1; i -= lowbit(i))
ans += bit[i];
return ans;
}
}bit[11][10];//bit[k][l%k]
int n, q, a[MAXN], op, t;
void update()
{
int l, r, k, c;
scanf("%d%d%d%d", &l, &r, &k, &c);
bit[k][l % k].update(l, c); bit[k][l % k].update(r + 1, -c);
}
void query()
{
int num, sum;
scanf("%d", &num);
sum = a[num];
for (int k = 1; k <= 10; k++)
sum += bit[k][num % k].query(num);//只有i%k==l%k才成立
printf("%d\n", sum);
}
int main()
{
while (scanf("%d", &n) != EOF)
{
memset(bit, 0, sizeof (bit));
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
scanf("%d", &q);
for (int i = 1; i <= q; i++)
{
scanf("%d", &op);
if (op == 1) update();
if (op == 2) query();
}
}
return 0;
}
结语
这题最巧妙的地方就是通过开多个树状数组把前提条件和区间维护分离了,前提条件通过不同的k和a%k把维护分到不同的树状数组上。
其实,这题十分能够体现数据结构在查询和维护的中间作用,但是我没能讲清楚,等我有了更深的理解如果想起来了或许会回来改。