bzoj 1878 [SDOI2009]HH的项链

【题意】

多组询问区间内不同颜色的数量, 不强制在线。

(好喜欢这种题意简单清晰的题啊,很优美。)

【题解】

果然区间离线操作不带修改的万能做法就是莫队了, 好想又好写。

#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)转移莫队就会挂。

用一些东西维护一个前缀和也是区间问题的经典思路。

你可能感兴趣的:(bzoj 1878 [SDOI2009]HH的项链)