NKOJ 2182 (HEOI 2012) 采花(树状数组/线段树)

P2182【河北OI 2012 DAY1】采花

问题描述

萧芸斓是Z 国的公主,平时的一大爱好是采花。
今天天气晴朗,阳光明媚,公主清晨便去了皇宫中新建的花园采花。花园足够大,容纳
了n 朵花,花有c 种颜色(用整数1-c 表示),且花是排成一排的,以便于公主采花。
公主每次采花后会统计采到的花的颜色数,颜色数越多她会越高兴!同时,她有一癖好,
她不允许最后自己采到的花中,某一颜色的花只有一朵。为此,公主每采一朵花,要么此前
已采到此颜色的花,要么有相当正确的直觉告诉她,她必能再次采到此颜色的花。
由于时间关系,公主只能走过花园连续的一段进行采花,便让女仆福涵洁安排行程。福
涵洁综合各种因素拟定了m 个行程,然后一一向你询问公主能采到多少朵花(她知道你是编
程高手,定能快速给出答案!),最后会选择令公主最高兴的行程(为了拿到更多奖金!)。

输入格式

第一行四个空格隔开的整数n、c 以及m。
接下来一行n 个空格隔开的整数,每个数在[1, c]间,第i 个数表示第i 朵花的颜色。
接下来m 行每行两个空格隔开的整数l 和r(l ≤ r),表示女仆安排的行程为公主经
过第l 到第r 朵花进行采花。

输出格式

共m 行,每行一个整数,第i 个数表示公主在女仆的第i 个行程中能采到的花的颜色数。

样例输入

5 3 5
1 2 2 3 1
1 5
1 2
2 2
2 3
3 5

样例输出

2
0
0
1
0

数据范围

对于20%的数据,n ≤ 10^2,c ≤ 10^2,m ≤ 10^2;
对于50%的数据,n ≤ 10^5,c ≤ 10^2,m ≤ 10^5;
对于100%的数据,1 ≤ n ≤10^5,c ≤ n,m ≤ 10^5。


主要是两种思路,都是离线算法。
做法一:
C[i] 表示从1到i总共出现的不同颜色且出现次数大于1的花的数量,那么预处理出每一朵花的下一个和他同颜色的花的位置,记到 next[x] 数组中
令当前讨论的左端点为 x ,那么当x左移的时候,对C[i]产生的影响是 next[x] next[next[x]]1 位置的 C[i] 减一,于是将区间排序,用线段树维护 C[i] ,进行区间操作即可。


代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#define N 200005
using namespace std;
struct node{int x,y,id,ans;}B[N];
bool cmp1(node a,node b)
{return a.x<b.x;}
bool cmp2(node a,node b)
{return a.id<b.id;}
int n,c,m,A[N],NE[N],LA[N],C[N],cnt[N];
int ls[N*10],rs[N*10],v[N*10],lazy[N*10],tot;
int BT(int x,int y)
{
    int p=++tot;
    if(x<y)
    {
        int mid=x+y>>1;
        ls[p]=BT(x,mid);
        rs[p]=BT(mid+1,y);
    }
    else v[p]=C[x];
    return p;
}
void PD(int p)
{
    v[ls[p]]+=lazy[p];
    v[rs[p]]+=lazy[p];
    lazy[ls[p]]+=lazy[p];
    lazy[rs[p]]+=lazy[p];
    lazy[p]=0;
}
void MD(int p,int l,int r,int x,int y,int d)
{
    if(lazy[p])PD(p);
    if(x<=l&&y>=r){lazy[p]+=d;v[p]+=d;return;}
    int mid=l+r>>1;
    if(x<=mid&&y>=l)MD(ls[p],l,mid,x,y,d);
    if(x<=r&&y>mid)MD(rs[p],mid+1,r,x,y,d);
}
int GS(int p,int l,int r,int k)
{
    if(lazy[p])PD(p);
    if(l==r)return v[p];
    int mid=l+r>>1;
    if(k<=mid)return GS(ls[p],l,mid,k);
    return GS(rs[p],mid+1,r,k);
}
int main()
{
    int i,j,k;
    scanf("%d%d%d",&n,&c,&m);
    for(i=1;i<=n;i++)scanf("%d",&A[i]);
    for(i=n;i>=1;i--)
    {
        if(!LA[A[i]])NE[i]=n+1;
        else NE[i]=LA[A[i]];
        LA[A[i]]=i;
    }
    NE[n+1]=n+1;
    for(i=1;i<=n;i++)
    {
        cnt[A[i]]++;
        C[i]=C[i-1];
        if(cnt[A[i]]==2)C[i]++;
    }
    BT(1,n);
    for(i=1;i<=m;i++)scanf("%d%d",&B[i].x,&B[i].y),B[i].id=i;
    sort(B+1,B+m+1,cmp1);i=1;j=1;
    while(j<=m)
    {
        while(B[j].x>i)MD(1,1,n,NE[i],NE[NE[i]]-1,-1),i++;
        while(B[j].x==i)B[j].ans=GS(1,1,n,B[j].y),j++;
    }
    sort(B+1,B+m+1,cmp2);
    for(i=1;i<=m;i++)printf("%d\n",B[i].ans);
}

做法二:
始终将从当前讨论位置往后,每种颜色的第二次出现的位置的地方为1,其他地方为0,那么维护前缀和数组即可求出答案。
因此,同样用 next 数组,然后用树状数组维护前缀和。当讨论到一个询问的左端点时,右端点的前缀和值就是答案,只需要始终维护从当前讨论位置开始,每种颜色第二次出现的位置即可。


代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#define N 200005
using namespace std;
struct node{int x,y,id,ans;}B[N];
bool cmp1(node a,node b)
{return a.x<b.x;}
bool cmp2(node a,node b)
{return a.id<b.id;}
int n,c,m,A[N],NE[N],LA[N],C[N],D[N];
void MD(int x,int d)
{for(int i=x;i<=n;i+=(i&-i))D[i]+=d;}
int GS(int x)
{
    int i,sum=0;
    for(i=x;i;i-=(i&-i))sum+=D[i];
    return sum;
}
int main()
{
    int i,j,k;
    scanf("%d%d%d",&n,&c,&m);
    for(i=1;i<=n;i++)scanf("%d",&A[i]);
    NE[n+1]=n+1;
    for(i=n;i>=1;i--)
    {
        if(!LA[A[i]])NE[i]=n+1;
        else NE[i]=LA[A[i]];
        LA[A[i]]=i;
        MD(NE[i],1);
        MD(NE[NE[i]],-1);
    }

    for(i=1;i<=m;i++)scanf("%d%d",&B[i].x,&B[i].y),B[i].id=i;
    sort(B+1,B+m+1,cmp1);
    i=1;k=1;
    while(k<=m)
    {
        while(i<B[k].x)MD(NE[i],-1),MD(NE[NE[i]],1),i++;
        while(i==B[k].x)B[k].ans=GS(B[k].y),k++;
    }
    sort(B+1,B+m+1,cmp2);
    for(i=1;i<=m;i++)printf("%d\n",B[i].ans);
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值