Can you answer these queries?
GSS系列是spoj出品的一套数据结构好毒瘤题,主要以线段树、平衡树和树链剖分为背景,进行了一些操作的魔改,使得难度远超模板题,但对于思维有极大的提升。
所以我会选择一些在我能力范围内的题挖坑选讲,构成一个GSS系列。至于剩下那些,等我成为巨佬弄懂了再说吧。
GSS4:区间开平方
这道题可能是GSS系列中的一个另类吧,因为它并没有涉及到最大子段和,而且难度也不是特别大,是一个经典的套路题。但其精妙之处就在于数据结构与暴力的完美结合。
题意
给定一段区间,要求支持区间开平方(下取整)和查询区间和。
口胡
这题真的没什么可以图解的
本题最大的问题在于区间开方后不能在 O ( 1 ) O(1) O(1)时间内维护区间和。但是不管你用什么办法,你永远无法把区间开平方和区间和联系起来。所以此题需要另辟蹊径。
我们会发现,如果对一个数不断开方,那么这个数字会很快变小,最后变为1,然后不管你怎么开方,都不会再改变。对于题目中的数据,大概对一个数开六到七次平方,就会变成1。于是我们可以想到,对于每次开平方操作,我们都暴力单点修改区间,如果修改后整段区间都变为了1,就在线段树上打标记,下次修改时直接跳过此区间,而区间和直接用线段树维护即可。复杂度仍旧可以看做 O ( n l o g n ) O(nlogn) O(nlogn),只不过有比较大的常数。
具体实现中,如何判断整个区间为1是最重要的。主要有两种实现方式:
- 线段树同时维护区间最大值,如果区间最大值已经为1,那么整个区间一定均小于等于1(可能为0)。
- 线段树每个节点增加一个 t a g tag tag,表示这段区间是否为1,一个节点的tag在push_up时由两个子节点进行与运算得到,只有两个子节点均为1时,父亲节点才为1.
代码
区间最大值实现
#include <bits/stdc++.h>
#define reset(x) memset(x, 0, sizeof(x))
#define lc(x) (x<<1)
#define rc(x) (x<<1|1)
#define mid (l+r>>1)
#define ll long long
#define MAX 100005
using namespace std;
int n, m, t;
ll a[MAX], sum[MAX*4], ma[MAX*4];
void push_up(int p) {
sum[p] = sum[lc(p)]+sum[rc(p)];
ma[p] = max(ma[lc(p)], ma[rc(p)]);
}
void build(int p, int l, int r) {
if(l == r) {
sum[p] = ma[p] = a[l];
return;
}
build(lc(p), l, mid);
build(rc(p), mid+1, r);
push_up(p);
}
void update(int p, int l, int r, int ul, int ur) {
if(l>=ul && r<=ur && ma[p] <= 1) {
return;
}
if(l == r) {
sum[p] = ma[p] = (ll)sqrt(sum[p]);
return;
}
if(mid >= ul) {
update(lc(p), l, mid, ul, ur);
}
if(mid < ur) {
update(rc(p), mid+1, r, ul, ur);
}
push_up(p);
}
ll query(int p, int l, int r, int ul, int ur) {
if(l>=ul && r<=ur) {
return sum[p];
}
ll res = 0;
if(mid >= ul) {
res += query(lc(p), l, mid, ul, ur);
}
if(mid < ur) {
res += query(rc(p), mid+1, r, ul, ur);
}
return res;
}
void solve() {
reset(ma);
reset(a);
reset(sum);
for(int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
}
if(t>1) puts("");
printf("Case #%d:\n", t);
build(1, 1, n);
cin >> m;
int t, x, y;
for(int i = 1; i <= m; i++) {
scanf("%d%d%d", &t, &x, &y);
if(x > y)
swap(x, y);
if(t == 1) {
printf("%lld\n", query(1, 1, n, x, y));
} else {
update(1, 1, n, x, y);
}
}
}
int main() {
while(cin >> n){
t++;
solve();
}
return 0;
}
tag实现
#include <bits/stdc++.h>
#define MAX 100005
#define ll long long
#define lc(x) (x<<1)
#define rc(x) (x<<1|1)
#define mid ((l+r)>>1)
using namespace std;
template <typename T>
inline void read(T &n){
n = 0;
char c = getchar();
while(!isdigit(c) && c != '-') c = getchar();
int f = 1;
if(c == '-') f = -1, c = getchar();
while(isdigit(c)) n = (n<<3)+(n<<1)+c-'0', c = getchar();
n *= f;
}
template <typename T>
void write(T n){
if(n < 0) putchar('-'), n = -n;
if(n>9) write(n/10);
putchar(n%10+'0');
}
int n, m;
ll a[MAX], s[MAX*4], tag[MAX*4];
inline void push_up(int p){
s[p] = s[lc(p)]+s[rc(p)];
tag[p] = (tag[lc(p)] & tag[rc(p)]);
}
void build(int p, int l, int r){
if(l == r){
s[p] = a[l];
if(a[l] <= 1) tag[p] = 1;
return;
}
build(lc(p), l, mid);
build(rc(p), mid+1, r);
push_up(p);
}
void update(int p, int l, int r, int ul, int ur){
if(tag[p]) return;
if(l == r){
s[p] = (int)sqrt(s[p]);
if(s[p] <= 1) tag[p] = 1;
return;
}
if(mid >= ul) update(lc(p), l, mid, ul, ur);
if(mid < ur) update(rc(p), mid+1, r, ul, ur);
push_up(p);
}
ll query(int p, int l, int r, int ul, int ur){
if(l >= ul && r <= ur){
return s[p];
}
ll res = 0;
if(mid >= ul) res += query(lc(p), l, mid, ul, ur);
if(mid < ur) res += query(rc(p), mid+1, r, ul, ur);
return res;
}
int main()
{
read(n);
for(int i = 1; i <= n; i++){
read(a[i]);
}
build(1, 1, n);
read(m);
int t, l, r;
for(int i = 1; i <= m; i++){
read(t), read(l), read(r);
if(l>r) swap(l, r);
if(t == 1){
write(query(1,1,n, l, r));
puts("");
}
else{
update(1,1,n, l, r);
}
}
return 0;
}