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();
}

P4690 Ynoi2016 镜中的昆虫的更多相关文章

  1. [Ynoi2016]镜中的昆虫

    题目大意: 给定一个序列,有2个操作: 1. 区间覆盖.2. 区间数颜色. 解题思路: 珂朵莉树+树套树. 看到区间覆盖当然想到珂朵莉树然而这是Ynoi 所以我们得优化掉珂朵莉树那个暴力过程. 考虑对 ...

  2. bzoj4941: [Ynoi2016]镜子里的昆虫

    维护每个位置x的上一个相等的位置pv[x],可以把询问表示成l<=x<=r,pv[x]<l的形式,对一次修改,均摊改变O(1)个pv的取值,因此可以用平衡树预处理出pv的变化,用cd ...

  3. 2021record

    2021-10-14 P2577 [ZJOI2004]午餐 2021-10-13 CF815C Karen and Supermarket(小小紫题,可笑可笑) P6748 『MdOI R3』Fall ...

  4. Codeforces 453E - Little Pony and Lord Tirek(二维线段树+ODT)

    Codeforces 题目传送门 & 洛谷题目传送门 一道难度 *3100 的 DS,而且被我自己搞出来了! 不过我终究还是技不如人,因为这是一个 \(n\log^2n\) + 大常数的辣鸡做 ...

  5. ZJOI2019Round#1

    考的这么差二试基本不用去了 不想说什么了.就把这几天听课乱记的东西丢上来吧 这里是二试乱听课笔记ZJOI2019Round#2 ZJOI Round#1 Day1 M.<具体数学>选讲 罗 ...

  6. 2.lvm动态逻辑卷

    Lvm动态逻辑卷 一.             基本概念 LVM 把实际的物理磁盘数据映射到一个简单而灵活的虚拟逻辑存储视图上,藉以控制磁盘资源: 也就是重新考虑了管理文件系统和卷的方法,在文件系统管 ...

  7. VR定制开发、AR定制开发(长年承接虚拟现实、增强现实应用、VR游戏定制开发,北京公司,可签合同)

    Cardboard SDK for Unity的使用 上一篇文章作为系列的开篇,主要是讲了一些虚拟现实的技术和原理,本篇就会带领大家去看一看谷歌的Cardboard SDK for Unity,虽然目 ...

  8. 承接Unity3D外包公司 — 技术分享

    Cardboard SDK for Unity的使用 上一篇文章作为系列的开篇,主要是讲了一些虚拟现实的技术和原理,本篇就会带领大家去看一看谷歌的Cardboard SDK for Unity,虽然目 ...

  9. 阿里前CEO卫哲用自己10余年经历,倾诉B2B的三差、四率、两大坑

    今日(12 月 28 日),嘉御基金创始人.阿里巴巴(B2B)前 CEO 卫哲在第三届中国 B2B 电子商务大会上进行了"B2B 冬天里的春天"的主题分享.他提出中国 B2B 行业 ...

  10. 合金装备V 幻痛 制作技术特辑

    合金装备V:幻痛 制作特辑 资料原文出自日版CGWORLD2015年10月号   在[合金装备4(Metal Gear Solid IV)]7年后,序章作品[合金装备5 :原爆点 (Metal Gea ...

随机推荐

  1. C primer plus笔记之初识C语言

    初识C语言 --本文参考书籍:         Stephen Prata的<C Primer Plus> 前言 C 语言是一门抽象的.面向过程的语言,C 语言广泛应用于底层开发,C 语言 ...

  2. 这应该是全网最详细的Vue3.5版本解读

    前言 Vue3.5正式版在这两天发布了,网上已经有了不少关于Vue3.5版本的解读文章.但是欧阳发现这些文章对3.5中新增的功能介绍都不是很全,所以导致不少同学有个错觉,觉得Vue3.5版本不过如此, ...

  3. Mongodb入门2

    $push 修饰符:用于往数组中追加元素  db.workmates.update({name:'xiaowang'},{$push:{interseta:"PHP"}})    ...

  4. 鸿蒙应用开发:如何与组件库(Glide)衔接?

    ​ Android 发展到现在不仅提供了很多 API,还提供了很多第三方库.这降低了我们开发者的开发难度,提升了开发效率,让应用开发更加的简单高效.众所周知,HarmonyOS 除了提供 16000  ...

  5. 『面试题』WEB前端面试专题-Promise相关

    题目一 const promise = new Promise((resolve, reject) => { console.log(1); resolve(); console.log(2); ...

  6. manim边学边做--弧形多边形

    弧形多边形是一种结合了圆弧和多边形的图形,这类几何图形在设计中应用非常广泛. 比如在家居设计中,看看家里的沙发,餐桌和座椅等,它们的边角,靠背等地方都是弧形的设计,这种设计有效柔化了室内空间,使整体氛 ...

  7. DQL—查询操作

    一.查询语法 select 字段列表 from 表名列表 where 条件列表 group by 分组列表 having 分组后条件 order by 排序字段 limit 分页限定 (提供一个表来操 ...

  8. Hive--hbase--spark

    hive创建hbase表 create external table events.hb_train( row_key string, user_id string, event_id string, ...

  9. JDK线程池详解(全网最全-原理解析、源码详解)

    频繁创建新线程的缺点? 不受控风险 系统资源有限,每个人针对不同业务都可以手动创建线程,并且创建标准不一样(比如线程没有名字).当系统运行起来,所有线程都在疯狂抢占资源,毫无规则,不好管控. 另外,过 ...

  10. Android复习(二)应用资源——>样式

    样式资源定义界面的格式和外观.样式可应用于单个 View(从布局文件中)或应用于整个 Activity 或应用(从清单文件中). 如需详细了解如何创建和应用样式,请参阅样式和主题. 注意:样式是使用  ...