离线对于线段树是一个简单的进阶技巧,必须掌握!下面由两个例题引入。
HDU-3333
对于这类题,我们发现,它们都是比较难以区间整体修改的题目,但是,单点修改比较容易,这个题单点的和就是它本身,但是,若想修改区间,我们必须判重。比较难搞。所以,我们可以考虑离线化。其实,我们利用单点修改可以想到:如果一个数字在我们查询的区间已经存在了,那我们就把他change为0,我们将所以询问按照右端点从小到大排序。然后从左向右依次进行单点修改,我们保证:
使每个数始终是在我们已经遍历的区间最后一个出现
例如:
对于数组:
1 1 1 2 3 2 1 6
如果现在我们遍历到第6位,那咱们就数组change为这样:
0 0 1 0 3 2 1 6
意思是,在已经遍历过的区间中,对于重复元素,咱们只保留一个,且是最后一次出现的那个。
问啥呢:
我们边遍历,边匹配询问,一但遍历到某些询问的右节点,我们就可以ask出答案了!因为数字一直都是靠后出现的,所以,不会少计。
下面是ac代码:
#include <iostream>
#include <string>
#include <cstring>
#include <queue>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#define ll long long
using namespace std;
const int N = 200005;
const int inf = 0x3f3f3f3f;
struct Node
{
ll mx;
int l, r;
}tr[N<<2];
struct qy
{
int id;
int l, r;
ll ans;
}qu[N];
int n;
int ind[N];
ll su[N];
bool vis[N];
int lisan[N];
int _mx, _mi;
int mm = 0;
int cnt;
void build(int p, int l, int r)
{
mm = max(mm, p);
tr[p].l = l; tr[p].r = r;
if (l == r) {tr[p].mx = 0; return;}
int mid = (l + r) >> 1;
build(p<<1, l, mid);
build(p<<1|1, mid +1, r);
tr[p].mx = 0;
}
ll ask_mx(int p, int l, int r)
{
if (tr[p].l >= l && tr[p].r <= r) {return tr[p].mx;}
int mid = (tr[p].l + tr[p].r) >> 1;
ll val = 0;
if (l <= mid) val += ask_mx(p<<1, l, r);
if (r > mid) val += ask_mx(p<<1|1, l, r);
return val;
}
void change(int p, int k, ll v)
{
if (tr[p].l == tr[p].r) {tr[p].mx = v; return;}
int mid = (tr[p].l + tr[p].r) >> 1;
if (k <= mid) change(p<<1, k, v);
else change(p<<1|1, k, v);
tr[p].mx = tr[p<<1].mx + tr[p<<1|1].mx;
}
bool cmp(qy a, qy b)
{
return a.r < b.r;
}
bool _cmp(qy a, qy b)
{
return a.id < b.id;
}
int fi(int i)
{
return lower_bound(lisan + 1, lisan + n + 1, su[i]) - lisan;
}
int main()
{
int t;
cin >> t;
while(t--)
{
memset(vis, 0, sizeof(vis));
memset(ind, 0, sizeof(ind));
scanf("%d", &n);
build(1,1,n);
for (int i = 1; i <= n; i++)
{
scanf("%lld", &su[i]);
lisan[i] = su[i];
}
sort(lisan + 1, lisan + n + 1);
int q;
scanf("%d", &q);
for (int i = 0; i < q; i++)
{
scanf("%d%d", &qu[i].l, &qu[i].r);
qu[i].id = i;
}
sort(qu, qu + q, cmp);
int j = 0;
for (int i = 1; i <= n; i++)
{
int gg = fi(i);
//cout <<i <<"---------------------" <<endl;
if (vis[gg])
{
change(1, ind[gg], 0);
change(1, i, su[i]);
ind[gg] = i;
}
else
{
vis[gg]=1;
change(1, i, su[i]);
ind[gg] = i;
}
// for (int i = 1; i <= mm; i++)
// {
// cout << tr[i].l << " " <<tr[i].r <<" " <<tr[i].mx << endl;
// }
for (;j < q; j++)
{
if (i != qu[j].r) break;
qu[j].ans = ask_mx(1, qu[j].l, qu[j].r);
}
}
sort(qu, qu + q, _cmp);
for (int i = 0; i < q; i++)
{
printf("%lld\n", qu[i].ans);
}
}
return 0;
}
HDU-4417
这个题的离散化难想一点,首先,我们吧数组按高度排序,在把询问按高度排序:然后维护一个树状数组:区间中能达到高度的数量。我们从低到高解决询问,也从低到高匹配数字。如果这个数字的值小于等于这个询问的值,咱们就在数组的对应位置加一,这样就可以了。
下面是ac代码:
#include <iostream>
#include <string>
#include <cstring>
#include <queue>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#define ll long long
using namespace std;
const int N = 200005;
const int inf = 0x3f3f3f3f;
int c[N], ans[N];
struct qy
{
int l, r;
int h;
int ind;
int ans;
}qu[N];
struct po
{
int ind;
int h;
}su[N];
int n, m;
bool cmp(qy a, qy b)
{
return a.h < b.h;
}
bool cmp1(po a, po b)
{
return a.h < b.h;
}
void add(int x, int y)
{
for (;x <= n; x += x & -x) c[x] += y;
}
int ask(int x)
{
int ans = 0;
for (; x; x -= x &-x) ans += c[x];
return ans;
}
int main()
{
int t;
int t0 = 1;
cin >> t;
while(t--)
{
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i++)
{
scanf("%d", &su[i].h);
su[i].ind = i;
}
for (int i = 0; i < m; i++)
{
scanf("%d%d%d", &qu[i].l, &qu[i].r, &qu[i].h);
qu[i].ind = i;
}
sort(su, su + n, cmp1);
sort(qu, qu + m, cmp);
memset(c, 0, sizeof(c));
int j = 0;
for (int i = 0; i < m; i++)
{
while(su[j].h <= qu[i].h && j < n)
{
add(su[j].ind + 1, 1);
j++;
}
ans[qu[i].ind] = ask(qu[i].r + 1) - ask(qu[i].l);
}
printf("Case %d:\n", t0++);
for (int i = 0; i < m; i++)
printf("%d\n", ans[i]);
}
return 0;
}