首页 > 其他分享 >P4690 Ynoi2016 镜中的昆虫

P4690 Ynoi2016 镜中的昆虫

时间:2024-08-14 16:16:19浏览次数:10  
标签:pre int mid Ynoi2016 P4690 区间 镜中 it2 it1

P4690 Ynoi2016 镜中的昆虫

原题不会见祖宗。

前置

珂朵莉树、cdq 分治、树状数组

思路

单点修改区间查询

定义 \(pre_i\) 表示 \(col_i\) 的前一个一样颜色的位置,那么对于一段区间查询 \([l,r]\),我们只需要查询有区间内有多少个 \(pre_i< l\)。

每次修改时就相当于修改四个同颜色的点的 \(pre\) 值,用 \(set\) 维护每个颜色的编号,树套树维护区间的 \(pre\) 值,一次修改就可以变为若干次树套树上的修改。

但实际上也可以使用 cdq 分治,先按照时间排序,对于时间区间 \([l,r]\) 取其终点 \(mid=\frac{l+r}{2}\),使 \([l,mid]\) 时刻中的修改对 \((mid,r]\) 的询问做贡献。

然后按照修改的 \(pre\) 值小于查询的 \(l\),依次插入树状数组。

但这样我们的 cdq 分治的排序会比较复杂,需要维护查询 \((time,l,r,ans,type)\),修改 \((time,pos,pre,val,type)\),这两个五元组。

我们可以考虑把查询和修改拆开拆成两个数组,\(mid\) 为时刻 \([L,R]\) 的中点,每次找到修改时刻小于 \(mid\) 的部分 \([l_1,mid_1]\) 和查询时刻小于 \(mid\) 的部分 \([l_2,mid_2]\),使用 \([l_1,mid_1]\) 对 \((mid_2,r_2]\) 贡献,下传 \((l_1,mid_1,l_2,mid_2,L,mid)\) 和 \((mid_1+1,r_1,mid_2+1,r_2,mid,R)\),就可以让你的代码优美一点。

inline void solve(int l1,int r1,int l2,int r2,int L,int R)
{
    if(l1==r1||l2==r2||L==R) return ;
    int mid=(L+R)>>1;
    int mid1=l1;while(mid1<r1&&md[mid1+1].t<=mid) mid1++;
    int mid2=l2;while(mid2<r2&&qr[mid2+1].t<=mid) mid2++;
    solve(l1,mid1,l2,mid2,L,mid);solve(mid1,r1,mid2,r2,mid,R);
    if(l1!=mid1&&mid2!=r2)
    {
        sort(md+l1+1,md+mid1+1);sort(qr+mid2+1,qr+r2+1);
        for(int i=mid2+1,j=l1+1;i<=r2;i++)
        {
            while(j<=mid1&&md[j].pre<qr[i].l) T.updata(md[j].pos,md[j].val),j++;
            qr[i].ans+=T.getsum(qr[i].r)-T.getsum(qr[i].l-1);
        }
        for(int i=l1+1;i<=mid1;i++) T.del(md[i].pos);
    }
}
//md 是修改,qr 是查询

区间修改区间查询

结论:对一个长度为 n 的数组进行 m 次区间赋值操作,pre 数组的改变次数为 \((n+m)\) 级别

我们只需要每次对于一个区间推平 \([l,r]\),如果其内部有一个区间的颜色是相同的,现在需要将其赋值为其他的颜色,那么只有 \(l\) 的 \(pre\) 值,和右端点右侧第一个同色点的 \(pre\) 值会改变。

根据这个性质,我们可以把颜色相同的区间看做一个点,所以 \(pre\) 的改变是删去节点个数,一开始有 \(n\) 个节点,\(m\) 次修改,总复杂度在 \(O(n+m)\) 级别。

具体的修改可以参考珂朵莉树的区间推平,一个 set 维护所有的区间和颜色。\([l,r]\) 端点的落下区间 split 拆开,然后把 \([l,r]\) 中的所有区间删除。删除时的 \(pre\) 变成了什么可以每个颜色开一个 set 维护,注意每个颜色的 set 和整体的 set 是同步的。

具体看代码吧。

CODE

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e5+5;

int n,m;
int a[maxn];
int pre[maxn],npre[maxn];
int tp[maxn],lf[maxn],rt[maxn],co[maxn];

#define SNI set <nod> :: iterator
#define SDI set <data> :: iterator

struct modi{int t,pos,pre,val;friend bool operator<(modi a,modi b){return a.pre<b.pre;}}md[10*maxn];int tp1;
struct qry{int t,l,r,ans;friend bool operator<(qry a,qry b){return a.l<b.l;}}qr[maxn];int tp2,cnt;
inline bool cmp(qry a,qry b){return a.t<b.t;}

inline void modify(int pos,int co)//将 pos 点的 pre 值改成 co(原来的 pre 值是 npre[pos])
{
    if(npre[pos]==co) return ;
    md[++tp1]=(modi){++cnt,pos,npre[pos],-1};
    md[++tp1]=(modi){++cnt,pos,npre[pos]=co,1};
}
namespace preview
{
    int lst[2*maxn];
    map<int,int>mp;
    inline void prew()
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]),mp[a[i]]=1;
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d%d",&tp[i],&lf[i],&rt[i]);
            if(tp[i]==1) scanf("%d",&co[i]);
            mp[co[i]]=1;
        }
        map<int,int>::iterator it,it1;//离散化
        for(it=mp.begin(),it1=it,it1++;it1!=mp.end();it1++,it++) it1->second+=it->second;
        for(int i=1;i<=n;i++) a[i]=mp[a[i]];for(int i=1;i<=m;i++) co[i]=mp[co[i]];
        for(int i=1;i<=n;i++) pre[i]=lst[a[i]],lst[a[i]]=i;
        for(int i=1;i<=n;i++) npre[i]=pre[i];
    }
}
namespace colist//区间修改
{
    struct data {int l,r,x;friend bool operator<(data a,data b){return a.r<b.r;}};set <data> s;
    struct nod {int l,r;friend bool operator<(nod a,nod b){return a.r<b.r;}};set <nod> c[2*maxn];set <int> bd;
    //bd 维护了等待更改的 l 点
    inline void split(int mid)//把区间 [l,r] 拆成 [l,mid-1] 和 [mid,r]
    {
        SDI it=s.lower_bound((data){0,mid,0});data p=*it;
        if(mid==p.r) return ;
        s.erase(p);s.insert((data){p.l,mid,p.x});s.insert((data){mid+1,p.r,p.x});
        c[p.x].erase((nod){p.l,p.r});c[p.x].insert((nod){p.l,mid});c[p.x].insert((nod){mid+1,p.r});
    }
    inline void del(set <data> :: iterator it)//删除一个区间
    {
        bd.insert(it->l);SNI it1,it2;
        it1=it2=c[it->x].find((nod){it->l,it->r});
        it2++;if(it2!=c[it->x].end()) bd.insert(it2->l);
        c[it->x].erase(it1);
        s.erase(it);
    }
    inline void ins(data p)//加入一个区间
    {
        s.insert(p);
        SNI it=c[p.x].insert((nod){p.l,p.r}).first;
        it++; if(it!=c[p.x].end()) bd.insert(it->l);
    }
    inline void stv(int l,int r,int x)
    {
        if(l!=1) split(l-1);split(r);int p=l;//
        while(p!=r+1){SDI it=s.lower_bound((data){0,p,0});p=it->r+1;del(it);}
        ins((data){l,r,x});
        for(auto it=bd.begin();it!=bd.end();it++)//对 l 点 pre 进行更新
        {
            auto it1=s.lower_bound((data){0,*it,0});
            if(*it!=it1->l) modify(*it,*it-1);
            else
            {
                auto it2=c[it1->x].lower_bound((nod){0,*it});
                if(it2!=c[it1->x].begin()) it2--,modify(*it,it2->r);
                else modify(*it,0);
            }
        }
        bd.clear();
    }
    inline void ih()
    {
        int nc=a[1];int ccnt=1;
        for(int i=2;i<=n;i++)
            if(nc!=a[i]){s.insert((data){i-ccnt,i-1,nc});c[nc].insert((nod){i-ccnt,i-1});nc=a[i],ccnt=1;}
            else ccnt++;
        s.insert((data){n-ccnt+1,n,nc});c[a[n]].insert((nod){n-ccnt+1,n});
    }
}
namespace cdq
{
    struct treearray//树状数组
    {
        const int K=maxn-5;
        int tree[maxn];
        int lowbit(int x){return x&(-x);}
        void updata(int x,int y){for(;x<=K;x+=lowbit(x)) tree[x]+=y;}
        void del(int x){for(;x<=K;x+=lowbit(x)) tree[x]=0;}
        int getsum(int x){int sum=0;for(;x;x-=lowbit(x)) sum+=tree[x];return sum;}
        void clr(){for(int x=1;x<=K;x++) tree[x]=0;}
    }T;
    int srt[maxn];
    inline bool cmp1(int a,int b){return pre[a]<pre[b];}
    inline void solve(int l1,int r1,int l2,int r2,int L,int R)//cdq 分治
    {
        if(l1==r1||l2==r2||L==R) return ;
        int mid=(L+R)>>1;
        int mid1=l1;while(mid1<r1&&md[mid1+1].t<=mid) mid1++;
        int mid2=l2;while(mid2<r2&&qr[mid2+1].t<=mid) mid2++;
        solve(l1,mid1,l2,mid2,L,mid);solve(mid1,r1,mid2,r2,mid,R);
        if(l1!=mid1&&mid2!=r2)
        {
            sort(md+l1+1,md+mid1+1);sort(qr+mid2+1,qr+r2+1);
            for(int i=mid2+1,j=l1+1;i<=r2;i++)
            {
                while(j<=mid1&&md[j].pre<qr[i].l) T.updata(md[j].pos,md[j].val),j++;
                qr[i].ans+=T.getsum(qr[i].r)-T.getsum(qr[i].l-1);
            }
            for(int i=l1+1;i<=mid1;i++) T.del(md[i].pos);
        }
    }
    inline void mainsolve()
    {
        colist::ih();
        for(int i=1;i<=m;i++)
            if(tp[i]==1) colist::stv(lf[i],rt[i],co[i]);
            else qr[++tp2]=(qry){++cnt,lf[i],rt[i],0};
        sort(qr+1,qr+tp2+1);for(int i=1;i<=n;i++) srt[i]=i;sort(srt+1,srt+n+1,cmp1);
        for(int i=1,j=1;i<=tp2;i++)//对于初始的颜色不进行 cdq
        {
            while(j<=n&&pre[srt[j]]<qr[i].l) T.updata(srt[j],1),j++;
            qr[i].ans+=T.getsum(qr[i].r)-T.getsum(qr[i].l-1);
        }
        T.clr();
        //cdq 中的贡献一定是修改给予的
        sort(qr+1,qr+tp2+1,cmp);solve(0,tp1,0,tp2,0,cnt);sort(qr+1,qr+tp2+1,cmp);
        for(int i=1;i<=tp2;i++) printf("%d\n",qr[i].ans);
    }
}

int main()
{
    preview::prew();cdq::mainsolve();
}

标签:pre,int,mid,Ynoi2016,P4690,区间,镜中,it2,it1
From: https://www.cnblogs.com/binbinbjl/p/18359238

相关文章

  • P4688 Ynoi2016 掉进兔子洞
    P4688Ynoi2016掉进兔子洞经典莫队加bitset。思路不难发现最终答案就是:\[(r_1-l_1+1)+(r_2-l_2+1)+(r_3-l_3+1)-3\timessize\]其中\(size\)表示3个区间内出现了多少个公共元素。看到这么多区间,不妨有把区间拆下来搞莫队的想法。先不考虑询问个数的限制,我们考虑使用......
  • C117 莫队配合 bitset P4688 [Ynoi2016] 掉进兔子洞
    视频链接:C117莫队配合bitsetP4688[Ynoi2016]掉进兔子洞_哔哩哔哩_bilibili   LuoguP4688[Ynoi2016]掉进兔子洞//莫队配合bitsetO(n*sqrt(n))#include<iostream>#include<cstring>#include<algorithm>#include<cmath>#include<bitset>usin......
  • P4690 [Ynoi2016] 镜中的昆虫
    P4690[Ynoi2016]镜中的昆虫本质就是区间修改,区间数颜色弱化一下,先考虑不带修的情况,也就是P1972[SDOI2009]HH的项链其难点在于区间颜色的去重,需要想一个办法让区间内一个颜色只被数一次我们可以去维护一个\(Nxt\)数组,表示下一个与当前位置颜色相同的位置若当前位......
  • LCR 183. 望远镜中最高的海拔(双端队列)
    科技馆内有一台虚拟观景望远镜,它可以用来观测特定纬度地区的地形情况。该纬度的海拔数据记于数组 heights ,其中 heights[i] 表示对应位置的海拔高度。请找出并返回望远镜视野范围 limit 内,可以观测到的最高海拔值。示例1:输入:heights=[14,2,27,-5,28,13,39],limit=......
  • P4690 [Ynoi2016] 镜中的昆虫 题解
    题目链接:镜中的昆虫经典题了,我们首先回顾下颜色数的常见做法统计:对每个位置维护一个\(pre_i\),表示与当前位置相同的颜色上一次出现位置。那么我们分讨一下。查询\([l,r]\)得到颜色数,对于\(pre_i<l\)的\(i\)点,显然它就是这个区间内\(a_i\)对应颜色出现的第一个位置,我们......
  • P4688 [Ynoi2016] 掉进兔子洞
    题意给定长度为\(n\)的序列\(s\)。有\(m\)个询问,每次询问三个区间,把三个区间中同时出现的数一个一个删掉,问最后三个区间剩下的数的个数和,询问独立。Sol不难发现答案即为求:\(r1-l1+r2-l2+r3-l3+3-siz\)。其中\(siz\)表示三个区间的公共颜色的个数。仔细......
  • [Ynoi2016] 镜中的昆虫
    64MB,1.5s题目描述您正在欣赏galgame的HS,然后游戏崩溃了,于是您只能做数据结构题了:维护一个长为 \(n\) 的序列 \(a_i\),有 \(m\) 次操作。将区间 \([l,r]\) 的值修改为 \(x\)。询问区间 \([l,r]\) 出现了多少种不同的数,也就是说同一个数出现多次只算一个。......
  • [Ynoi2016] 这是我自己的发明(根号分治+分块/莫队)
    题目传送门soltion简单题换根显然可以拆成\(O(1)\)个区间,这里先不管。直接做法是莫队,把双子树拆成\(dfs\)序上的双前缀,可以直接莫队,但是常数比较大。另一种做法是根分,对颜色出现次数分治,大于的求出\(dfs\)序的前缀和即可,小于的因为一共只有\(O(n\sqrtn)\)个点对,所以......
  • luogu P4690 [Ynoi2016] 镜中的昆虫 题解
    P4690[Ynoi2016]镜中的昆虫题解题意维护一个长为 \(n\) 的序列 \(a_i\),有 \(m\) 次操作。将区间 \([l,r]\) 的值修改为 \(x\)。询问区间 \([l,r]\) 出现了多少种不同的数,也就是说同一个数出现多次只算一个。题解感觉这道题还是比较有意思的,像是一堆套路......
  • 镜中
    镜中只要想起一生中后悔的事梅花便落了下来比如看她游泳到河的另一岸比如登上一株松木梯子危险的事固然美丽不如看她骑马归来面颊温暖,羞惭。低下头,回答着皇帝一面镜子永远等候她让她坐到镜中常坐的地方望着窗外,只要想起一生中后悔的事梅花便落满了南山这是我最喜欢的......