洛谷 4113 [HEOI2012]采花 题解(树状数组,瞎搞)

33 篇文章 0 订阅

(看到这题目让我想起了采花大盗。。。)
(好的,正式开始)
原题链接:
洛谷
bzoj

题意简述

求区间出现次数 > = 2 >=2 >=2的。

数据

输入
n c m
//数列长度,每个数的规模,询问个数(n,m,c<=2e6)
l r
l r
...
l r
//m个,1<=l<=r<=n,问l,r中有多少出现次数>=2的
输出
ans
ans
...
ans
//m行,对于每个询问,输出答案
样例

输入

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

输出

2
0
0
1
0

思路

理论上这个题是主席树做的。。。
但是我主席树一直咕咕咕,出来会打板子,别的不会。
所以我就点开这里 不不不是这里看到了一个神奇的做法如下:
搞一个 n e x t next next数组, n e x t [ i ] next[i] next[i]表示最小的 j j j满足 a [ j ] = = a [ i ] a[j]==a[i] a[j]==a[i],也就是下一个相等的位置。这个 n e x t next next数组是我们处理这一类出现次数问题的常用方法。然后,我们搞一个 n e x t 2 [ i ] = n e x t [ n e x t [ i ] ] next2[i]=next[next[i]] next2[i]=next[next[i]]。因为我们要求出现次数 &gt; = 2 &gt;=2 >=2的,所以这个玩意珂能会有用。
(以上两个数组,如果没有满足条件的数,就等于 0 0 0

如果我们要查全局,那么我们只要找多少个 i i i满足 a [ i ] a[i] a[i]是第二次出现的即珂。然后我们把每个位置上的这个表达式的值算出来,设一个 01 01 01数组( b o o l bool bool数组)为 T T T。即:
T [ i ] = { 1 a [ i ] 是 第 二 次 出 现 0 其 它 情 况 T[i]= \begin{cases} 1 &amp; &amp; &amp; &amp; &amp; &amp; &amp; &amp; &amp; &amp; a[i]是第二次出现\\ 0 &amp; &amp; &amp; &amp; &amp; &amp; &amp; &amp; &amp; &amp; 其它情况 \end{cases} T[i]={10a[i]
那么 T T T就相当于一个位置对答案的贡献值了。然后我们用树状数组维护这个 T T T。要求答案的话,区间求一下和即珂。

那么,如何进行修改工作呢?我们发现,如果只考虑局部,得到的 T T T数组和全局中得到的 T T T数组珂能是不一样的。因为有些数会出现同时出现在区间外面和里面,就会导致 n e x t next next数组值不同。现在的问题就是:我们如何通过全局的 T T T值求得局部的 T T T值,进而用树状数组的区间和求出答案。
这样我们已经很显然就能想到离线了。那么,如何排序?
画张图:
tmp.jpg
(绿色的区间是询问)
我们有一个 f a n t a s y ♂ fantasy♂ fantasy,对于某一种颜色,如果当前的区间错过了这个点,那么以后就别想再要回来了。如果真就这样,我们枚举到一个点要滚蛋的时候,设这个是 i i i,只要在 n e x t [ i ] next[i] next[i]位置减一(因为这样的话, n e x t [ i ] next[i] next[i]在接下来的区间中,如果出现,只会是第一个位置,观察上图中的 i i i在第 2 , 3 2,3 2,3个询问时状态的变化)。同时,在 n e x t 2 [ i ] next2[i] next2[i]的位置上还要加一。因为此时 n e x t 2 [ i ] next2[i] next2[i]那边就珂能是第 2 2 2个位置,就有珂能让答案加一。

Q:如果 n e x t 2 [ i ] next2[i] next2[i]并不是第 2 2 2个位置,而是第 1 1 1个位置,就不会对答案产生贡献了,但是那个加一还在鸭。。。怎么肥四。。。
A:确实, n e x t 2 [ i ] next2[i] next2[i]也珂能是第 1 1 1个位置。但是如果真的这样的话, n e x t [ i ] next[i] next[i]肯定也滚蛋了一次。在 n e x t [ i ] next[i] next[i]滚蛋的时候,就会把 n e x t 2 [ i ] next2[i] next2[i]上面的加一操作给减掉。

此时,我们知道,为了 f u l f i l l ♂ fulfill♂ fulfill这个 f a n t a s y ♂ fantasy♂ fantasy,我们要把区间按 l l l排序(也就是按左端点排序),然后离线求得答案,按原序输出。

整理一下思路:

  1. 求出最开始的贡献数组 T T T
  2. 离线,将询问按 l l l排序,处理出滚蛋的位置,区间求和计算答案
  3. 按读入顺序排序,顺序输出答案

代码:

// luogu-judger-enable-o2
#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define N 2001000
    class BIT//树状数组,BIT是binary indexed tree的简写
    {
        public:
            int tree[N];
            int len;
            void BuildTree(int n)
            {
                len=n;
                memset(tree,0,sizeof(tree));
            }
            void Add(int pos,int x)
            {
                while(pos<=len)
                {
                    tree[pos]+=x;
                    pos+=(pos&(-pos));
                }
            }
            int Query(int pos)
            {
                int ans=0;
                while(pos>0)
                {
                    ans+=tree[pos];
                    pos-=(pos&(-pos));
                }
                return ans;
            }
            int RQuery(int l,int r)
            {
                return Query(r)-Query(l-1);
            }
    }T;

    struct Q1//一个询问
    {
        int l,r;
        int id,ans;//记得保存id,到最后要顺序输出
    }Q[N];
    bool cmp_l(Q1 a,Q1 b){return a.l<b.l;}
    bool cmp_id(Q1 a,Q1 b){return a.id<b.id;}

    int n,c,m;
    int a[N];
    void R1(int &x)
    {
        x=0;char c=getchar();int f=1;
        while(c<'0' or c>'9') f=(c=='-')?-1:1,c=getchar();
        while(c>='0' and c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
        x=(f==1)?x:-x;
    }
    void Input()
    {
        R1(n),R1(c),R1(m);
        for(int i=1;i<=n;++i)
        {
            R1(a[i]);
        }
    }

    int nxt[N],nxt2[N];
    int tmp[N];
    void Soviet()
    {
        for(int i=n;i>=1;--i)
        {
            int c=a[i];
            nxt[i]=tmp[c];
            tmp[c]=i;
        }
        for(int i=1;i<=n;++i)
        {
            nxt2[i]=nxt[nxt[i]];
        }//求出nxt和nxt2

        memset(tmp,0,sizeof(tmp));//此时把tmp设置为0,改变定义为:tmp[i]是第i中颜色出现的次数
        T.BuildTree(n);
        for(int i=1;i<=n;++i)
        {
            int c=a[i];
            if (++tmp[c]==2)//第二次出现
            {
                T.Add(i,1);//记录贡献
            }
        }

        for(int i=1;i<=m;++i)
        {
            int l,r;
            R1(l),R1(r);
            Q[i]=(Q1){l,r,i,-1};//此时没有答案,设置ans为-1
        }

        sort(Q+1,Q+m+1,cmp_l);//按l排序
        int pos=1;//上一个区间的l
        for(int i=1;i<=m;++i)
        {
            for(;pos<Q[i].l;++pos)//这些是要滚蛋的点
            {
                if (nxt[pos]) T.Add(nxt[pos],-1);
                if (nxt2[pos]) T.Add(nxt2[pos],1);
            }
            Q[i].ans=T.RQuery(Q[i].l,Q[i].r);//区间求和
        }
        sort(Q+1,Q+m+1,cmp_id);//按读入顺序排序,输出答案
        for(int i=1;i<=m;++i)
        {
            printf("%d\n",Q[i].ans);
        }
    }

    void IsMyWife()
    {
        if (0)
        {
            freopen("","r",stdin);
            freopen("","w",stdout);
        }
        Input();
        Soviet();
    }
    #undef int //long long
};
int main()
{
    Flandle_Scarlet::IsMyWife();
    return 0;
}

回到总题解界面

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值