[SDOI2009]HH的项链

本文讲解了一种利用右端点排序和贪心策略解决区间内不同数计数问题的方法。通过线段树或莫队结构,博主详细阐述了如何通过更新操作和树状数组求前缀和来计算每个区间的独特数。核心算法涉及预处理数组pre和贡献计数cnt的动态调整。
摘要由CSDN通过智能技术生成

题目链接

个人感觉这是一道很精妙的写法,首先如果在线求的话不好求,那么考虑离线,离线的话,肯定要排序,那就得看按左端点
排序还是按右端点排序,这里按右端点排序,至于为什么等下就知道了。我们要找的是区间内不同数的个数,因为按照
右端点排序了,所以根据贪心的想法,如果一个数在一个一直往右扩张的区间中,出现多次那么我们只要算最右边的那
个数的贡献就好了,所以我们找出每一个数的上一个数是多少然后使得该位置贡献-1(原来是1),然后现在这个位置+1
然后算答案的时候是sum(a[i].r)-sum(a[i].l-1),sum(x)是树状数组求前缀和,意思为前x个数有多少个不同的的数,
实在不理解可以看看样例 1 1 1 2 3 设cnt[x]表示x这个点的贡献,pre[i]表示第i个数的前一个数的位置
pre[i]={01200};
当 x=1时 cnt[1]=1,sum[1]=1;
当 x=2时 cnt[1]=1-1=0(pre数组的作用),cnt[2]=1,sum[2]=1;
当 x=3时 cnt[1]=0,cnt[2]=0,cnt[3]=1,sum[3]=1;
当 x=4时 cnt[1]=0,cnt[2]=0,cnt[3]=0,cnt[4]=1,sum[4]=2;
PS:线段树,莫队好像都可以写的样子,莫队可能被卡,线段树相对而言代码多。
#include <algorithm>
#include <deque>
#include <iomanip>
#include <iostream>
#include <map>
#include <math.h>
#include <queue>
#include <set>
#include <stack>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <unordered_map>
#include <vector>
#define ll long long int
#define ms(a, b) memset(a, b, sizeof(a))
#define lowbit(x) (x & -x)
#define fi first
#define se second
#define Size(a) int((a).size())
#define ull unsigned long long
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define endl "\n"
#define bug cout << "----acac----" << endl;
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
using namespace std;
const ll mod = 1e9 + 7;
const int maxn = 1e6 + 10;
const int maxm = 5e3 + 50;
const double eps = 1e-8;
const ll inf = 0x3f3f3f3f;
const ll lnf = 0x3f3f3f3f3f3f3f3f;
const double pi = acos(-1);
int n, m;
int a[maxn];
int b[maxn];
struct segement
{
    int l, r;
    int id;
} p[maxn];
bool cmp(segement A,segement B)
{
    return A.r < B.r;
}
int pre[maxn];
int Next[maxn];
void update(int x,int val)
{
    while(x<=n)
    {
        a[x] += val;
        x += lowbit(x);
    }
}
int query(int x)
{
    int ans = 0;
    while(x>0)
    {
        ans += a[x];
        x -= lowbit(x);
    }
    return ans;
}
int ans[maxn];

int main()
{
    IOS;
    cin >> n;
    for (int i = 1; i <= n;i++)
    {
        cin >> b[i];
        pre[i] = Next[b[i]];
        Next[b[i]] = i;
    }
    cin >> m;
    for (int i = 1; i <= m;i++)
    {
        cin >> p[i].l >> p[i].r;
        p[i].id = i;
    }
    sort(p + 1, p + 1 + m, cmp);
    int mx = 1;
    for (int i = 1; i <= m;i++)
    {
        while(mx<=p[i].r&&mx<=n)
        {
            update(mx, 1);
            if(pre[mx])
            {
                update(pre[mx], -1);
            }
            mx++;
        }
        ans[p[i].id] = query(p[i].r) - query(p[i].l - 1);
    }
    for (int i = 1; i <= m;i++)
    {
        cout << ans[i] << endl;
    }
        return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值