Turing Tree
题目链接: Turing Tree HDU - 3333
题意
给你一N个数字,有Q个询问,每次询问一个区间,求这个区间中不同的数字之和 (N < 3e4,Q < 1e5)
思路
首先,最简单想到的肯定是暴力算法,但是暴力算法一定会T,复杂度为O(N*Q)
所以我们要想办法去优化一下,看到了Q次询问,和每次询问一个区间,那么莫队算法就呼之欲出了。
莫队算法的复杂度为O(n^(3/2))基本是可以过的,离线之王。
那么如何处理呢,最最重要的就是处理add和def中的算法,当add时,我们肯定希望res能够加上这个坐标的值,但是怎么确定这个值有没有出现过呢,我们用一个num[]数组来储存这个数字出现的次数,最好能有一个合适的下标,来代表这个数据,因为数据浮动范围过大,每个数据都是1~1e9,所以我们需要离散化,来优化一下下标,这里用了map来作为离散化工具,给每个数据附上一个独一无二的ID,用aim[]数组来储存一下,每个坐标所对应的ID即可。
map(int,int) mp; //将读入的数字找到他的ID
int aim[MAXN]; //coor -> ID
int num[MAXN]; //ID ->counts
int cnt; //give ID
代码一
#include <bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i = (int)j;i <= (int)k;i ++)
#define per(i,j,k) for(int i = (int)j;i >= (int)k;i --)
#define mmm(a,b) memset(a,b,sizeof(a))
#define pb push_back
typedef double db;
typedef long long ll;
const int MAXN = (int)1e5+7;
const int INF = (int)0x3f3f3f3f;
int teams; //每组的数量
struct Mo {
int l,r,id;
Mo (int l = 0,int r = 0,int id = 0):l(l),r(r),id(id){}
}e[MAXN];
bool cmp(const Mo&a,const Mo&b){
if (a.l / teams == b.l / teams)
return a.r < b.r;
return a.l/teams < b.l/teams;
}
map<int,int> mp; //将读入的数字找到他的ID
int aim[MAXN]; //coor -> ID
int num[MAXN]; //ID ->counts
int cnt; //give ID
int N,Q;
int A[MAXN];
ll ans[MAXN];
ll res;
void add(int p){
int id = aim[p];
if(num[id] == 0) res += (ll)A[p];
num[id] ++;
}
void def(int p){
int id = aim[p];
if(num[id] == 1) res -= (ll)A[p];
num[id] --;
}
void init(){
res = 0;
cnt = 0;
mmm(num,0);
mp.clear();
}
int main()
{
int T;
scanf("%d",&T);
while (T --){
init();
scanf("%d",&N);
teams = sqrt(N); // teams不要忘
rep(i,1,N) {
scanf("%d",&A[i]);
if (mp[A[i]] == 0){
mp[A[i]] = ++cnt;
}
aim[i] = mp[A[i]];
}
scanf("%d",&Q);
rep(i,1,Q) {
scanf("%d %d",&e[i].l,&e[i].r);
e[i].id = i;
}
sort(e+1,e+1+Q,cmp);
int pl = 5,pr = 4;
rep(i,1,Q) {
while (pr < e[i].r) add(++pr);
while (pl > e[i].l) add(--pl);
while (pr > e[i].r) def(pr--);
while (pl < e[i].l) def(pl++);
ans[e[i].id] = res;
}
rep(i,1,Q) {
printf("%lld\n",ans[i]);
}
}
}
思路二
采用离线线段树的手法,先将询问储存起来,对右节点进行排序,记录一个pos点,每次向着当前询问的右节点移动。然后用mp来映射每个值出现的最后一个位置。这个算法的核心思想就始终维护一个当前节点l到r的值每个都出现,并且都出现一次,再利用线段树的高效求区间和来求出答案,复杂度O(nlogn)
大神解读
这种线段树只能离线来写,离线的方法是按照查询区间的右端点来排序,然后这道题目的数据范围较大需要离散化简单处理一下,然后对于输入的每个点来说,顺序走下去,然后如果当前点之前出现过,便将之前的删除然后把现在的添加线段树中,为什么这么可以,看了网上神犇一句话,那就是对于要查询的区间,它的右端点固定后,那么重复的数字便是右面开始最后一次出现的 。
代码二
#include <bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i = (int)j;i <= (int)k;i ++)
#define per(i,j,k) for(int i = (int)j;i >= (int)k;i --)
#define mmm(a,b) memset(a,b,sizeof(a))
#define pb push_back
#define lson rt<<1
#define rson rt<<1|1
typedef double db;
typedef long long ll;
const int MAXQ = (int)1e5+7;
const int MAXV = (int)3e4+7;
const int INF = (int)0x3f3f3f3f;
struct Node {
int id,l,r;
Node(int id = 0,int l = 0,int r = 0):id(id),l(l),r(r){}
}e[MAXQ];
bool cmp(const Node &a,const Node &b){
if (a.r == b.r)
return a.l < b.l;
return a.r < b.r;
}
ll sum[MAXV<<2]; //线段树求sum
ll ans[MAXQ]; //ans stored
int num[MAXV]; //id -> val
map<int,int> mp; //val -> count
int N,Q;
void PushUp(int rt) {sum[rt] = sum[lson] + sum[rson];}
void Update(int L,int l,int r,int V,int rt){
if (l == r){
sum[rt] = V;
return;
}
int m = l+r>>1;
if(L <= m) Update(L,l,m,V,lson);
else Update(L,m+1,r,V,rson);
PushUp(rt);
}
ll Query(int L,int R,int l,int r,int rt){
if (L <= l && r <= R){
return sum[rt];
}
int m = l+r>>1;
ll ans = 0;
if (L <= m) ans += (ll)Query(L,R,l,m,lson);
if (R > m) ans += (ll)Query(L,R,m+1,r,rson);
return ans;
}
void init(){
mmm(sum,0);
mp.clear();
}
int main()
{
int T;
scanf("%d",&T);
while (T --){
init();
scanf("%d",&N);
rep(i,1,N) scanf("%d",&num[i]);
scanf("%d",&Q);
rep(i,1,Q) {
int l,r;
scanf("%d %d",&l,&r);
e[i] = Node(i,l,r);
}
sort(e+1,e+1+Q,cmp);
int pos = 0;
rep(i,1,Q){
while (pos < e[i].r) {
pos ++; //每次移动指针到询问的右边界
int pre = mp[num[pos]];
if (pre != 0){
Update(pre,1,N,0,1); //将前一次出现的位置清零
}
mp[num[pos]] = pos;
Update(pos,1,N,num[pos],1);
}
ans[e[i].id] = Query(e[i].l,e[i].r,1,N,1);
}
rep(i,1,Q) printf("%lld\n",ans[i]);
}
}