hdu 3333 Turing Tree(线段树/数状数组)或莫队算法

链接:http://acm.hdu.edu.cn/showproblem.php?pid=3333
题意:求区间不同数之和
首先我们发现询问的顺序对答案不会造成影响,那么我们可以考虑离线算法(将询问存下来,在以一种特定的顺序排序,来降低复杂度)。
这个题求不同的数之和,那么重复元素不会对ans造成影响,我们可以对询问的右端点升序排序,那么我们可以pos_k遍历arr数组,对(1,pos_k)这段区间我们维护他的元素唯一性(区间的元素没有重复的),怎么维护呢,考虑到对于一次询问 的左端点是向右边走的计算答案,我们只需要维护每个元素的离他最近即可, 那么对于每次询问满足(r<=pos_k)是不是相当于求sum[r]-sum[l-1];
具体实现:

#include<bits/stdc++.h>
#define LL long long
#define lson(x) ((x)<<1)
#define rson(x) ((x)<<1|1)
using namespace std;
const LL maxn = 3e4 + 19;
struct tree {
    LL l, r,sum;
    LL id;
}tr[maxn<<2],qur[maxn<<2];
LL arr[maxn],ans[maxn<<2];
void push_up(LL pos)
{
    tr[pos].sum = tr[lson(pos)].sum + tr[rson(pos)].sum;
}
void build(LL l, LL r, LL pos)
{
    tr[pos].l = l; tr[pos].r = r;
    tr[pos].sum = 0;
    if (l == r)return;
    int mid  = l + r >> 1;
    build(l, mid, lson(pos));
    build(mid + 1, r, rson(pos));
}
void update(LL pos_in,LL pos_val, LL pos)//单点更新
{
    if (tr[pos].l == pos_in&&tr[pos].r == pos_in)
    {
        tr[pos].sum += pos_val;
        return;
    }
    int mid = tr[pos].l + tr[pos].r >> 1;
    if (mid >= pos_in) update(pos_in, pos_val, lson(pos));
    else update(pos_in, pos_val, rson(pos));
    push_up(pos);
}
LL ask(LL ll, LL rr, LL pos)
{
    if (ll == tr[pos].l&&tr[pos].r == rr)return tr[pos].sum;
    int mid = tr[pos].l + tr[pos].r >> 1;
    if (mid >= rr)  return ask(ll, rr, lson(pos));
    else if (mid < ll)  return ask(ll, rr, rson(pos));
    else return ask(ll, mid, lson(pos)) + ask(mid + 1, rr, rson(pos));
}
int main()
{
    LL t;
    scanf("%lld", &t);
    while (t--)
    {
        mp.clear();
        LL n, q;
        scanf("%lld", &n);
        for (LL i = 1; i <= n; ++i)
            scanf("%lld", &arr[i]);
        build(1, n, 1);
        scanf("%lld", &q);
        for (LL i = 1; i <= q; ++i)
        {
            scanf("%lld%lld", &qur[i].l, &qur[i].r);
            qur[i].id = i;
        }
        sort(qur + 1, qur + q + 1, [](tree&a, tree&b) {
            if (a.r == b.r)return a.l < b.l;
            return a.r < b.r;
        });
        LL pos_k = 1;
        map<LL, LL>mp;//ma[a] = pos表示值为a的元素在arr数组中的最后出现的位置;
        for (LL i = 1; i <= q; ++i)
        {
            for (; pos_k <= qur[i].r; ++pos_k)
            {
                if (mp.find(arr[pos_k]) != mp.end())//如果当前的元素在之前已经出现了,更新sum[mp[arr[pos_k]]]~sum[pos_k的值(减去arr[pos_k]);
                    update(mp[arr[pos_k]], -arr[pos_k], 1);

                mp[arr[pos_k]] = pos_k;
                update(pos_k, arr[pos_k], 1);//更新sum[pos_k];
            }
            ans[qur[i].id] = ask(qur[i].l, qur[i].r, 1);
        }
        for (LL i = 1; i <= q; ++i)
            printf("%lld\n", ans[i]);
    }
    return 0;
}

莫队算法:
这就是个裸的莫队算法;
但是,这个重复元素的标记,因为元素很大,如果用map,那会TLE的。。。我试过,,,,所以要离散化元素,再去重,那么每个元素所唯一对应一个下标,那么我们就可以愉快的用数组标记了。。。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 9;
struct node {
    int l, r;
    int id, conid;
}le[maxn];
int arr[maxn];
long long ans[maxn];
int pos[maxn];
int temp[maxn];
int mp[maxn];
bool cmp(node &a, node &b)
{
    if (a.conid == b.conid)
        return a.r < b.r;
    return a.conid < b.conid;
}
int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        memset(mp, 0, sizeof mp);
        int n, q;
        scanf("%d", &n);
        int len = sqrt(n)+2;
        for (int i = 1; i <= n; ++i)
        {
            scanf("%d", &arr[i]);
            temp[i] = arr[i];
        }
        sort(temp + 1, temp + n + 1);
        int k = unique(temp + 1, temp + n + 1) - temp;
        for (int i = 1; i <= n; ++i)
            pos[i] = lower_bound(temp + 1, temp + k-1, arr[i]) - temp;
        scanf("%d", &q);
        for (int i = 1; i <= q; ++i)
        {
            scanf("%d%d", &le[i].l, &le[i].r);
            le[i].id = i; 
            le[i].conid = le[i].l / len;
        }
        sort(le + 1, le + q + 1, cmp);
        long long sum = arr[1];
        int L = 1, R = 1;
        mp[pos[1]] = 1;

        for (int i = 1; i <= q; ++i)
        {
            while (R < le[i].r)
            {
                R++;
                if (mp[pos[R]] == 0)sum += arr[R];
                mp[pos[R]]++;
            }
            while (L > le[i].l)
            {
                L--;
                if (mp[pos[L]]==0)sum += arr[L];
                mp[pos[L]]++;
            }
            while (R > le[i].r)
            {
                mp[pos[R]]--;
                if (mp[pos[R]] == 0)sum -= arr[R];
                R--;
            }
            while (L < le[i].l)
            {
                mp[pos[L]]--;
                if (mp[pos[L]] == 0)sum -= arr[L];
                L++;
            }
            ans[le[i].id] = sum;
        }
        for (int i = 1; i <= q; ++i)printf("%lld\n", ans[i]);
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值