【题意】
多组询问区间内不同颜色的数量, 不强制在线。
(好喜欢这种题意简单清晰的题啊,很优美。)
【题解】
果然区间离线操作不带修改的万能做法就是莫队了, 好想又好写。
#include
#include
#include
#include
#include
using namespace std;
int n, m, lala[1000005], a[50005], cnt, sq, aa[50005], l = 1, r, now;
struct query{
int l, r, id, ans;
}q[200005];
bool cmp1(query a, query b){
if(a.l / sq == b.l / sq)return a.r < b.r;
return a.l / sq < b.l / sq;
}
bool cmp2(query a, query b){return a.id < b.id;}
inline void up(int x){
if(!aa[x])now ++; aa[x] ++;
}
inline void down(int x){
aa[x] --; if(!aa[x])now --;
}
int main()
{
scanf("%d", &n); sq = sqrt(n);
for(int i = 1; i <= n; i ++){
int x; scanf("%d", &x);
if(!lala[x])lala[x] = ++ cnt;
a[i] = lala[x];
}scanf("%d", &m);
for(int i = 1; i <= m; i ++)scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
sort(q + 1, q + m + 1, cmp1);
for(int i = 1; i <= m; i ++){
while(r < q[i].r)up(a[++ r]);
while(l > q[i].l)up(a[-- l]);
while(r > q[i].r)down(a[r --]);
while(l < q[i].l)down(a[l ++]);
q[i].ans = now;
}
sort(q + 1, q + m + 1, cmp2);
for(int i = 1; i <= m; i ++)printf("%d\n", q[i].ans);
return 0;
}
其实这题是有 nlogn 的算法的, 也挺好想的。
简单模拟一下算一个区间里有多少种颜色的过程, 有多少种颜色可以转化成 有多少个初始颜色的标记, 也就是说如果把一个点打上了初始颜色的标记, 它后面的所有点就都不可能被打上初始颜色的标记了, 然后从这个区间的 L 段开始打标记, 打到R端, 如果每次询问打一遍的话就是 n方的。
这道题显然要离线做, 所以把左端点排序, 考虑左端点每向前移一个位置, 它原来所在地方的标记就不会再被统计入答案, 而那个标记颜色的下一个颜色会对答案产生贡献。 所以只需要维护每一种颜色的第一个颜色 和 每个位置的下一个 颜色和它相同的位置在哪里就可以了。
所以把左端点扫一遍, 每扫到一个点把 下一个颜色和它相同的位置 标记加一, 记录前缀和然后用树状数组维护一下就好了。
写起来还是很简单。
#include
#include
#include
#include
#include
#define lowbit(x) x & (-x)
#define MAXN 50005
using namespace std;
int n, m, a[MAXN], maxx, f[MAXN], next[MAXN], p[1000005];
inline void add(int x, int v){for(; x <= n; x += lowbit(x))f[x] += v; }
int ask(int x){
int ret = 0;
for(; x; x -= lowbit(x))ret += f[x]; return ret;
}
struct query{int l, r, id, ans;}q[MAXN * 5];
bool cmp1(query a, query b){return a.l == b.l ? a.r < b.r : a.l < b.l;}
bool cmp2(query a, query b){return a.id < b.id;}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i ++)scanf("%d", &a[i]), maxx = max(maxx, a[i]);
for(int i = n; i; i --)next[i] = p[a[i]], p[a[i]] = i;
for(int i = 1; i <= maxx; i ++)if(p[i])add(p[i], 1);
scanf("%d", &m);
for(int i = 1; i <= m; i ++)scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
sort(q + 1, q + m + 1, cmp1);
int l = 1;
for(int i = 1; i <= m; i ++){
while(l < q[i].l){
if(next[l])add(next[l], 1);
l ++;
} q[i].ans = ask(q[i].r) - ask(q[i].l - 1);
}sort(q + 1, q + m + 1, cmp2);
for(int i = 1; i <= m; i ++)printf("%d\n", q[i].ans);
return 0;
}
Result:
算法 | Problem | Result | Memory | Time | Language | Code_Length |
莫队 | 1878 | Accepted | 8712 kb | 3340 ms | C++/Edit | 1309 B |
树状数组 | 1878 | Accepted | 9676 kb | 2516 ms | C++/Edit | 1358 B |
其实时间上也没有差太多,,
【技巧总结】
还是那句话, 区间离线无修改大多可以用莫队, 但是如果有的时候相邻的状态之间不能O(1)(或在特定情况下的logn)转移莫队就会挂。
用一些东西维护一个前缀和也是区间问题的经典思路。