洛谷 P1972 [SDOI2009]HH的项链

P1972 [SDOI2009]HH的项链

法一:树状数组,离线

翻译:

给出一个数列a[n]还有许多请求,请求由l,r两个数组成,要求对于每个请求输出数列中从a[l]到a[r]中不重复的数的个数。

方法:

首先读入数列a[n],并预处理next1,boo两个辅助数组,方法见程序。

然后读入请求,把请求按l从小到大排序,并记录原来序号方便输出。

(本题中,为了方便说明,用tree[i]表示可通过sum(i,i)求出的树状数组中第i项的值(不是指c[i]))

开始前,对于每个第一次出现的数所在的位置t,使tree[t]++,即add(t,1)。这样,如果请求的l是1,就可以通过sum(l,r)得出答案。但是,如果请求的左端点大于1,就不能这样做了。

例如,当请求的l是5时,在a[5]到a[r]中,可能有一些数与a[1]到a[4]中的数相同,且它们如果从第1个数开始数不是第一次出现的,但如果从第5个数开始数就是第一次出现的。因此,在处理这个请求的时候,就需要先找到tree[1]到tree[4]中所有不为0的数tree[x],先将tree[x]减1(由于tree[1]到tree[4]实际上不会影响sum(5,r)的结果,因此不减也没关系),再找到与a[x]相同的下一个数t,也就是next1[x]的值,然后将tree[t]加1(如果next1[x]为0,指的就是没有下一个与a[x]相同的数,则不需进行此操作)。例如有一个数tree[1]为1,那么就先将tree[1]减1(不减也行),然后如果next1[1]不为0,则将tree[next1[1]]加1。

由此可得出,需要按排序后的顺序来处理请求,对于请求q[i],如果q[i-1].l<q[i].l,则需要将tree[q[i-1].l]到tree[q[i].l-1]都按以上的方法进行处理,之后才能用sum(q[i].l,q[i].r)得出结果。

最后不要忘了按原来的询问顺序输出结果。

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long LL;
LL a[51000];
LL c[400100];
LL next1[51000];//next1[i]记录第i个数字下一次出现的位置
LL ans[201000];
LL boo[1001000];//boo[i]记录数字i第一次出现位置
LL n,m,x,k=1,lastk;
struct query1
{
    LL l,r;
    LL num,ans;
    friend bool operator<(query1 a,query1 b)
    {
        return (a.l==b.l&&a.r<b.r)||a.l<b.l;//如果a.r<=b.r则RE,不明原因??
    }
}q[200100];
LL lowbit(LL x)
{
    return x&-x;
}
void add(LL num,LL x)
{
    while(num<=n)
    {
        c[num]+=x;
        num+=lowbit(num);
    }
}
LL sum1(LL x)
{
    LL ans=0;
    while(x>0)
    {
        ans+=c[x];
        x-=lowbit(x);
    }
    return ans;//曾经忘记返回值 
}
LL sum(LL l,LL r)
{
    return sum1(r)-sum1(l-1);
}
int main()
{
    LL i,j,p;
    scanf("%lld",&n);
    for(i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        a[i]++;
    }
    for(i=n;i>=1;i--)//优化为倒序
        next1[i]=boo[a[i]],boo[a[i]]=i;//方便找到某数字第一次出现位置与下一次出现的位置(链式的),弄几组数据试试就能明白
    for(i=1;i<=n;i++)
        if(boo[a[i]]==i)
            add(i,1);
    scanf("%lld",&m);
    for(i=1;i<=m;i++)
    {
        scanf("%lld%lld",&q[i].l,&q[i].r);
        q[i].num=i;
    }
    sort(q+1,q+m+1);
    for(i=1;i<=m;i++)
    {
        //if(q[i].l>q[i-1].l)//后面去掉的,因为没用 
        //{
        lastk=k;
        k=q[i].l;
        for(j=lastk;j<k;j++)
        {
            //add(j,-1);//后面去掉的,因为不需要更新,后面答案与此无关 
            if(next1[j]!=0)
                add(next1[j],1);
            //boo[j]=next1[j];//后面去掉的,因为boo数组不再有用
        }
        //}
        ans[q[i].num]=sum(q[i].l,q[i].r);
        //q[i].ans=sum(q[i].l,q[i].r);
    }
    for(i=1;i<=m;i++)
        printf("%lld\n",ans[i]);
    //sort(q+1,q+m+1,cmp2);
    //for(i=1;i<=m;i++)
        //printf("%lld\n",q[i].ans);//另一种输出方式
    return 0;
} 

法二:莫队算法


这里是洛谷P3613寄包柜的代码,基于C++实现: ```c++ #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int INF = 0x3f3f3f3f; const int MAXN = 5005; int n, m, q; int f[MAXN], c[MAXN], h[MAXN], t[MAXN], st[MAXN]; int main() { scanf("%d%d%d", &n, &m, &q); for (int i = 1; i <= n; ++i) scanf("%d", &f[i]); for (int i = 1; i <= m; ++i) scanf("%d", &c[i]); for (int i = 1; i <= q; ++i) { int l, r, k; scanf("%d%d%d", &l, &r, &k); for (int j = l; j <= r; ++j) st[j - l] = f[j] - c[k]; sort(st, st + r - l + 1); int hh = 0, tt = -1; for (int j = l; j <= r; ++j) { while (hh <= tt && st[hh] + c[k] < h[j - 1]) ++hh; if (hh <= tt) t[j] = max(t[j], st[hh] + c[k]); while (hh <= tt && st[tt] >= st[j - l]) --tt; h[j] = max(h[j], st[j - l] + c[k]); st[++tt] = st[j - l]; } for (int j = l; j <= r; ++j) f[j] = max(f[j], t[j]); } int ans = -INF; for (int i = 1; i <= n; ++i) ans = max(ans, f[i]); printf("%d\n", ans); return 0; } ``` 思路: 题目要求的是一个区间内的最大值,可以考虑使用线段树或者分块等数据结构,但是这道题的数据范围比较小,可以直接使用暴力。 考虑对于每次询问,对区间 $[l,r]$ 内的数值减去邮寄 $k$ 号包裹的费用,然后排序,设排序后的数组为 $st$,然后对于每个位置 $j$,维护两个值 $h_j$ 和 $t_j$,表示在 $j$ 位置之前,$st$ 中的最大值和次大值。这里可以使用单调队列维护次大值。然后对于每个位置 $j$,更新 $f_j$ 即可。最后输出所有位置的最大值即为答案。 时间复杂度:$O(nq\log n)$。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值