[SDOI2009]HH的项链

luogu 1972
提高+/ 省选-

题面:
输入长度为N(N<50000)的数列,进行M(M<=200000)次询问,每次询问一个区间,输出数列对应区间内出现不同数字的个数。

例:
1 2 3 4 3 5
[1, 3] : 3
[2, 5] : 4
[1, 5] : 5

思路:
离线查询。
1、读入数列,第一次读入时,将每个数第一次出现的位置启用。

int main()
{
    scanf("%d", &n);
        for(int i=1; i<=n; i++)
        {
            scanf("%d", &a[i]); //a[i]:原数列中i的值
            v[a[i]].push_back(i); //v[a[i]]:a[i]在数列中出现的位置
            if(v[a[i]].size()>1) continue;
            t[i] = 1; //t[i]:位置i上的数是否启用,供线段树查询使用; 初始值为0(未启用),一旦位置i的数启用,则变为1(启用)
            next[a[i]] = 1; //next[a[i]]:下一个未启用的a[i]在v[a[i]]中的位置
        } 
    ...
}

2、将每次询问记录,并按照左边界排序。

struct tQues //将每个询问作为结构体储存
{
    int left, right, num; //num:记录原顺序
    bool operator < (const tQues& rhs) const 
    {
        if(left!=rhs.left) return left<rhs.left;
        return right<rhs.right;
    } //重载运算符,以便按照左边界(left)排序
} q[MAXN*4]

int main()
{   
    ...
    scanf("%d", &m);
    for(int i=1; i<=m; i++)
    {
        int tmp1, tmp2;
        scanf("%d%d", &tmp1, &tmp2);
        q[i].left = min(tmp1, tmp2);
        q[i].right = max(tmp1, tmp2);
        q[i].num = i;
    }

    sort(q+1, q+m+1); //排序
    ...
}

3、按顺序处理询问,处理时记录上一次询问的左边界last_l(初值为1)。每次查询时,[last_l, l)区间内的值永远不会在接下来的查询内出现。我们可以遍历[last_l, l),对于其中的每一个值,启用它的下一个出现位置。

int main()
{
    ...
    int last_l = 1;
    for(int i=1; i<=m; i++)
    {
        int l = q[i].left, r = q[i].right; //取l,r为当前询问的左、右边界
        for(int k=last_l; k<l; k++) //遍历[last_l, l)
        {
            int tmp = a[k]; //取tmp为数列中k位置的值
            if(next[tmp] >= v[tmp].size()) continue; //如果next超出了v.size(),意味着tmp的所有位置都已经启用,所以跳过
            t[v[tmp][next[tmp]]] = 1; //启用tmp的下一个位置
            st.change(1, 1, n, v[tmp][next[tmp]], 1); //对该位置进行单点修改
            next[tmp] ++; //next[tmp]后移一位
        }
        ans[q[i].num] = st.querysum(1, 1, n, l, r); //ans[]按原序记录答案
        last_l = l; //更新last_l
    }
    ...
}

4、按原序输出

int main()
{
    ...
    for(int i=1; i<=m; i++)
        printf("%d\n", ans[i]);

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值