莫队真的是个神奇的东西 %%%
这个题我之前用线段树写过了,这次写发现可以用莫队搞,
题意:t组数据 对于每一组数据,给你n个数,m次查询,打印每次查询中l到r之间不同的数字的和.
思路:套莫队板子,区间移动时用num计数,当计数跨越边界时更新状态temp.这里a[i] 是1e9级别的,但是n缺很小(1 ≤ N ≤ 30,000),就是说a[i]的类别不多,需要一次映射
扩展:这个方法也可以求边界不是0的情况,比如问你l到r之间数字个数多于k的数字的和.
代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#ifdef Wang_Zhifeng
#define debug(x) printf("debug:%s=%d\n",#x,x);
//#define debug(x) cout << #x << ": " << x << endl;
#endif
const int MAXN = 3000010;
const int MAXM = 3000010;
struct Query {
int L, R, id;
} node[MAXM];
long long ans[MAXM];
int a[MAXN];
int num[MAXN];
int f[MAXN];
int cnt;
int n, m, unit;
bool cmp(Query a, Query b) {
if(a.L/unit!=b.L/unit)return a.L/unit < b.L/unit;
else return a.R < b.R;
}
void work() {
memset(num, 0, sizeof(num));
long long temp = 0;
int L = 1;
int R = 0;
for(int i = 0; i < m; i++) {
while(R < node[i].R) {
R++;
num[f[R]]++;
if(num[f[R]]==1)temp += a[R];
}
while(R > node[i].R) {
num[f[R]]--;
if(num[f[R]]==0)temp -= a[R];
R--;
}
while(L < node[i].L) {
num[f[L]]--;
if(num[f[L]]==0)temp -= a[L];
L++;
}
while(L > node[i].L) {
L--;
num[f[L]]++;
if(num[f[L]]==1)temp += a[L];
}
ans[node[i].id] = temp;
}
}
int main() {
#ifdef Wang_Zhifeng
#pragma comment(linker, "/STACK:102400000,102400000")
freopen("in.txt", "r", stdin);
setvbuf(stdout, NULL, _IOFBF, 1024);
std::ios::sync_with_stdio(false);
cin.tie(0);
#endif
int t;
scanf("%d", &t);
while(t--) {
cnt = 1;
int tmp;
map<int, int> mp;
scanf("%d", &n);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
tmp = mp[a[i]];
if(tmp==0) {
f[i] = cnt;
mp[a[i]] = cnt++;
} else {
f[i] = tmp;
}
}
scanf("%d", &m);
for(int i = 0; i < m; ++i) {
node[i].id = i;
scanf("%d%d", &node[i].L, &node[i].R);
}
unit = (int) sqrt(n);
sort(node, node+m, cmp);
work();
for(int i = 0; i < m; i++)
printf("%lld\n", ans[i]);
}
return 0;
}
附赠之前写的AC代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
#define ll long long
#define N 30005
#define M 100005
struct node {
int l, r;
ll val;
} tree[N<<2+10];
int f[N];//the pos which last numb's val in the front of it and equal the numb's val
map<int, int> mp;//hash from val to f
int val[N];// the val in array
void build(int k, int l, int r) {
tree[k].l=l;
tree[k].r=r;
if(l==r) {
if(f[l]==0)
tree[k].val=val[l];
return;
}
int mid=(l+r)>>1;
build(k<<1, l, mid);
build(k<<1|1, mid+1, r);
tree[k].val=tree[k<<1].val+tree[k<<1|1].val;
}
void change_point(int k, int pos, int val) {
if(tree[k].l==tree[k].r) {
tree[k].val=val;
return;
}
int mid=(tree[k].l+tree[k].r)>>1;
if(pos<=mid)
change_point(k<<1, pos, val);
else
change_point(k<<1|1, pos, val);
tree[k].val=tree[k<<1].val+tree[k<<1|1].val;
}
ll find_interval(int k, int l, int r) {
if(tree[k].l>=l&&tree[k].r<=r) {
return tree[k].val;
}
int mid=(tree[k].l+tree[k].r)>>1;
ll ans=0;
if(l<=mid) {
ans+=find_interval(k<<1, l, r);
}
if(r>mid) {
ans+=find_interval(k<<1|1, l, r);
}
return ans;
}
struct interval {
int l;
int r;
int pos=0;
};
interval query[M];
ll ans[M];
bool cmp(interval a, interval b) {
return a.r<b.r;
}
int main() {
std::ios::sync_with_stdio(false);
#ifdef LOCAL
freopen("in.txt", "r", stdin);
#endif
int t, n, m,pos;
scanf("%d", &t);
while(t--) {
scanf("%d", &n);
pos=1;
mp.clear();
for(int i=1; i<=n; ++i) {
scanf("%d", &val[i]);
f[i]=mp[val[i]];
mp[val[i]]=i;
}
build(1, 1, n);
scanf("%d", &m);
for(int i=1; i<=m; ++i) {
scanf("%d%d", &query[i].l, &query[i].r);
query[i].pos=i;
}
sort(query+1, query+1+m, cmp);
for(int i=1; i<=m; ++i) {
while(query[i].r>=pos) {
if(f[pos]!=0){
change_point(1, f[pos], 0);
change_point(1, pos, val[pos]);
}
pos++;
}
ans[query[i].pos]=find_interval(1, query[i].l, query[i].r);
}
for(int i=1; i<=m; ++i) {
printf("%lld\n", ans[i]);
}
}
return 0;
}
写都写了,就附上下面这个AC的代码的思路吧
思路:首先是构建一个线段树(树状数组也是可以的),就是正常的求和那类的,对所有的数字,记录它前面第一个和它相同的数字的下标记作f[i],把所有询问离线,按照右界排序,按照新的顺序去区间查询,这里注意的是当区间变动后,不需要关注左界的变化,当右界向右移动时,将线段树中加入的点按照从左到右的顺序去加入,并且将f[i]点的数清空(其实建树的时候就已经放进去了,这里完全可以只进行第二个置零操作…).所得到的就是结果了.
证明:因为是按照右区间排序的,那么对于每次询问,其右界均为最近更新的右边界,如果一个区间存在多个相同的数字,那么离右边界最近的一个必然存在其中,这个数字是这些值相同的数字中最近一次更新的一个,因为每次都会将前面的置零,这个操作做到了去重,而且不存在值相同的一组数中最右边的数字没有被记录而其他值被记录的情况出现.
扩展:其实我看到这个题的第一个想法是利用数字的或运算进行去重,但是不同的数字实在是太多了, bitset应该也可以解.不过多了一层计算ans的一层遍历(O(N)),可能会T.