数列游戏IV

ZZUOJ 10508: 数列游戏IV

题意

给定一个序列,长度为N,每次询问为一组区间[Li,Ri],输出Li到Ri中出现恰好两次的不同数的个数.
Input
第一行两个整数N和M,N表示序列长度,M表示询问次数.(N,M<=2*10^5)
第二行N个整数,表示序列.(序列中元素<=10^9)
以后M行,每行为Li和Ri,表示询问区间.(1<=Li<=Ri<=N)

分析

1 wzy学长选拔赛出的题,我发现超经典,奈何我弱鸡没写出来,看题解半天才明白
2 本题的关键就在于离线, 一次更新之后进行了m次查询,故称为离线查询,于是你可以将查询数组排序然后按右端点的前后求结果
3 用树状数组求解这种区间查询,单点更新(每次加入一个点),区间查询
4 本题还是重点在于自己的理解,注释也不好写
2 具体分析参见代码注释

参考代码


//......................................................离线树状数组
int n,m;
const int LEN = 2e5+100;
int tree[LEN];//树状数组
int ans[LEN];//答案数组
int ar[LEN];
int last[LEN];//last[i]上一个与ar[i]相等的元素的位置
map<int,int> ma;//存储每一个数对应的最后的位置
struct Q
{
    int l,r,ID;
};
Q q[LEN];
bool operator <(const Q &a,const Q &b)
{
    return a.r < b.r;
}
void modify(int x,int d)
{
    while(x <= n)
    {
        tree[x] += d;
        x += lowbit(x);
    }
}
int Query(int x)
{
    int sum = 0;
    while(x>0)
    {
        sum += tree[x];
        x -= lowbit(x);
    }
    return sum;
}

int main()
{

    cin>>n>>m;

    for(int i = 1; i <= n; ++i)
    {
        scanf("%d",&ar[i]);
        last[i] = ma[ar[i]];
        ma[ar[i]] = i;
    }
    for(int i = 1; i <= m; ++i)
    {
        scanf("%d %d",&q[i].l,&q[i].r);
        q[i].ID = i;
    }
    sort(q+1,q+m+1);
    int index = 1;
    /*树状数组的目的是进行快速求和, 我们可以假设求和的数组是C*/
    for(int i = 1; i <= n; ++i)
    {
        if(last[i]!=0)
        modify(last[i],1);//将上一个与这个元素相同的元素的位置+1,代表有一组
        int p = last[last[i]];
        if(p != 0)
        {
            modify(p,-2);/*如果有三个或者多个该元素,则需要-2,把+1抵消,并且把之前 p 和 last[i] 这个组合抵消
            int pp = last[p];
            if(pp != 0)//消除-2的影响
                modify(pp,1);
        }
        / 分析后得知C[i]只有三种可能的值,0,-1,1,

*/
        while(index <= m&&q[index].r == i)
        {

            ans[q[index].ID] =   Query(i) - Query(q[index].l-1);/*这个时候Query(i)就代表从1到i有多少个恰好两次的不同数,Query(q[index].l-1)则不是*/
            index ++;
        }
    }
    for(int i = 1; i <= m; ++i)
        printf("%d\n",ans[i]);






    return 0;
}

转载于:https://www.cnblogs.com/zzuzxy/p/8542634.html

你可能感兴趣的:(数列游戏IV)