luogu1972 [SDOI2009]HH的项链

链接

  https://www.luogu.org/problem/show?pid=1972

题解一

  就是个莫队裸题

代码一

//莫队 
#include <cstdio>
#include <algorithm>
#define maxn 300000
#define size 250
using namespace std;
int col[maxn], lp[maxn], N, M, q[maxn][2], tmp[maxn], c[maxn], num[maxn], cnt[maxn],
    ans[maxn];
bool cmp(int a, int b)
{
    return lp[q[a][0]]==lp[q[b][0]]?q[a][1]<q[b][1]:lp[q[a][0]]<lp[q[b][0]];
}
void input()
{
    int i;
    scanf("%d",&N);
    for(i=1;i<=N;i++)scanf("%d",col+i),tmp[i]=col[i];
    sort(tmp+1,tmp+N+1);
    for(i=1;i<=N;i++)col[i]=lower_bound(tmp+1,tmp+N+1,col[i])-tmp;
    scanf("%d",&M);
    for(i=1;i<=M;i++)scanf("%d%d",q[i],q[i]+1),num[i]=i;
    for(i=1;i<=N;i++)lp[i]=i/size;
    sort(num+1,num+M+1,cmp);
}
void solve()
{
    int l=0, r=0, ql, qr, i, t=0;
    for(i=1;i<=M;i++)
    {
        ql=q[num[i]][0], qr=q[num[i]][1];
        for(;l<ql;l++)if(l)if(--cnt[col[l]]==0)t--;
        for(l--;l>=ql;l--)if(++cnt[col[l]]==1)t++;l=ql;
        for(;r>qr;r--)if(--cnt[col[r]]==0)t--;
        for(r++;r<=qr;r++)if(r)if(++cnt[col[r]]==1)t++;r=qr;
        ans[num[i]]=t;
    }
}
int main()
{
    input();
    solve();
    for(int i=1;i<=M;i++)printf("%d\n",ans[i]);
    return 0;
}

题解二

  看了各种神犇题解之后,发现还有一套做法:
  先把相同颜色的串起来,next[i]表示下一个和i颜色相同的。那么一次询问[l,r]就是要找出i [l,r]且next[i]>r的元素个数。完成这个有两种方式,第一种是主席树,在线做就可以,但比较难写;第二种是树状数组,但是要离线。
  主席树:建一棵权值主席树,一次查询就是,先找出[1,r]对应的树上的哪些节点,然后在第l-1和第r棵线段树中分别拿出来这些节点,权值相减,求和就是答案。
  树状数组:原数列按照next递减的顺序排序,询问按照r递减排序。顺序处理所有询问,每次r减少时就把所有next大于r的位置上加一,查询直接求sum(r)-sum(l-1)即可。(个人感觉这个做法更有省选的感觉)

代码二

//树状数组
#include <cstdio>
#include <algorithm>
#define maxn 1000010
#define lowbit(x) (x&-x)
using namespace std;
int ta[maxn], q[maxn][2], numq[maxn], col[maxn], next[maxn], frt[maxn], num[maxn],
    ans[maxn], N, M;
bool cmp1(int a, int b){return next[a]>next[b];}
bool cmp2(int a, int b){return q[a][1]>q[b][1];}
void input()
{
    int i;
    scanf("%d",&N);
    for(i=1;i<=N;i++)scanf("%d",col+i),num[i]=i;
    for(i=N;i;i--)next[i]=frt[col[i]]?frt[col[i]]:N+1,frt[col[i]]=i;
    scanf("%d",&M);
    for(i=1;i<=M;i++)scanf("%d%d",q[i],q[i]+1),numq[i]=i;
    sort(num+1,num+N+1,cmp1);
    sort(numq+1,numq+M+1,cmp2);
}
void add(int *r, int pos, int v){for(;pos<=N;pos+=lowbit(pos))r[pos]+=v;}
int sum(int *r, int pos){int ans=0;for(;pos;pos-=lowbit(pos))ans+=r[pos];return ans;}
void solve()
{
    int l, r, i, p=1;
    for(i=1;i<=M;i++)
    {
        l=q[numq[i]][0], r=q[numq[i]][1];
        for(;next[num[p]]>r;p++)add(ta,num[p],1);
        ans[numq[i]]=sum(ta,r)-sum(ta,l-1);
    }
}
int main()
{
    input();
    solve();
    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、付费专栏及课程。

余额充值