CF练习题16 (毒瘤数据结构)
Lomsat gelral
把树拍成一条链,询问子树等于询问区间。
这样一看这道题就非常莫队。
但是有要求个数最多的颜色的编号,我们可以用线段树动态维护颜色的最大值。
这样,一个无脑莫队线段树的暴力就做出来了。
int n,a[N];
int dfn[N],nfd[N],cnt;
int b[N],siz[N];
vector<int>g[N];
inline void dfs(int u,int from){
dfn[u]=++cnt;nfd[cnt]=u;
b[cnt]=a[u];
siz[u]=1;
for(auto v:g[u]){
if(v==from)continue;
dfs(v,u);
siz[u]+=siz[v];
}
}
struct ask{
int l,r,id;
}q[N];
int pos[N],blo;
inline bool cmp(ask x,ask y){
if(pos[x.l]!=pos[y.l])return pos[x.l]<pos[y.l];
if(pos[x.l]&1)return x.r>y.r;
return x.r<y.r;
}
int ans[N];
int coln[N];
struct SegmentTree{
struct node{
int l,r,maxl,maxpos;
}tr[N<<2];
inline void push_up(int k){
tr[k].maxl=max(tr[lc].maxl,tr[rc].maxl);
if(tr[k].maxl==tr[lc].maxl)tr[k].maxpos+=tr[lc].maxpos;
if(tr[k].maxl==tr[rc].maxl)tr[k].maxpos+=tr[rc].maxpos;
}
inline void build(int k,int l,int r){
tr[k].l=l;tr[k].r=r;
if(l==r){
tr[k].maxl=0;
tr[k].maxpos=l;
return;
}
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
push_up(k);
}
inline void add(int k,int l,int r,int val){
if(tr[k].l>=l&&tr[k].r<=r){
tr[k].maxl+=val;
return;
}
int mid=(tr[k].l+tr[k].r)>>1;
if(mid>=l)add(lc,l,r,val);
if(mid<r)add(rc,l,r,val);
push_up(k);
}
}T;
inline void add(int x){
T.add(1,x,x,1);
}
inline void del(int x){
T.add(1,x,x,-1);
}
signed main(){
n=read();
blo=sqrt(n);
up(i,1,n){
a[i]=read();
pos[i]=(i-1)/blo+1;
}
int u,v;
up(i,1,n-1){
u=read();v=read();
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1,0);
up(i,1,n){
q[i].l=dfn[i];
q[i].r=dfn[i]+siz[i]-1;
q[i].id=i;
}
T.build(1,1,n);
sort(q+1,q+1+n,cmp);
int L=1,R=0;
up(i,1,n){
while(R<q[i].r)add(b[++R]);
while(R>q[i].r)del(b[R--]);
while(L>q[i].l)add(b[--L]);
while(L<q[i].l)del(b[L++]);
ans[q[i].id]=T.tr[1].maxpos;
}
up(i,1,n)write(ans[i],0);
return 0;
}
当然,这道题正解应该是 \(dsu\ on\ tree\)。
用树上启发式合并又该怎么做呢?
我们先考虑暴力,对每一个点搜索其子树,暴力查询自树中颜色最多的颜色。
这个的复杂度是 \(O(n^2)\) 的。
但是我们发现,对于每个节点 \(v\),最后一棵子树是不用清空的,因为做完那棵子树后可以把其结果直接加入 \(v\) 的答案中。
那么我们选择那颗子树为 \(v\) 的重儿子。
看起来没有什么优化,实际上复杂度为 \(O(n\log n)\)。
代码我就不放了。
Frogs and mosquitoes
恶心题。
很暴力的,你可以对每一个蚊子,向前枚举最远的青蛙,这个青蛙吃到了这只蚊子,舌头变长,判断能否吃到蚊子,如果不能,加入新的蚊子。
当然,这个暴力是 \(n^2\) 的,肯定过不了。
考虑优化。
在向前枚举青蛙这一步时,我们可不可以二分,似乎是不行的。因为有青蛙的是不满足单调性的,所以我们可以预处理一下青蛙,把所有区间会被前面的青蛙完全覆盖的青蛙去掉,这样,青蛙就可以满足单调性。
所以可以二分找到最左边的青蛙。
找到那只青蛙,把舌头伸长。这时就可以对蚊子在的一个集合进行二分。找出能够吃那些蚊子。
最后记得继续删除青蛙来满足数组的单调性。
复杂度 \(O(n\log n)\)。
int n,m;
struct frog{
int x,t,id;
}a[N];
int ansl[N],ansc[N];
inline bool cmp(frog x,frog y){
if(x.x==y.x)return x.t<y.t;
return x.x<y.x;
}
int maxl=0;
vector<frog>t;
int k=0;
inline int ask(int p){
int l=1,r=k,ans=0;
while(l<=r){
int mid=(l+r)>>1;
if(t[mid].x+t[mid].t>=p){
r=mid-1;
ans=mid;
}
else l=mid+1;
}
if(t[ans].x<=p&&t[ans].x+t[ans].t>=p) return ans;
return 0;
}
multiset<pii>s;
inline void work(int x){
while(x<k){
if(t[x+1].x+t[x+1].t<=t[x].x+t[x].t) t.erase(t.begin()+x+1),k--;
else break;
}
}
signed main(){
n=read();m=read();
up(i,1,n){
a[i].x=read();
a[i].t=read();
a[i].id=i;
ansl[i]=a[i].t;
}
sort(a+1,a+1+n,cmp);
t.push_back({0,0,0});
up(i,1,n){
if(a[i].x+a[i].t>maxl){
t.push_back(a[i]);
k++;
maxl=a[i].x+a[i].t;
}
}
int x,delt;
up(i,1,m){
x=read();delt=read();
int p=ask(x);
if(p==0){
s.insert({x,delt});
continue;
}
ansl[t[p].id]+=delt;
ansc[t[p].id]++;
t[p].t+=delt;
while(!s.empty()){
auto it=s.lower_bound({t[p].x,0});
if((it==s.end())||(t[p].x+t[p].t<(it->fi))) break;
ansl[t[p].id]+=(it->se);
t[p].t+=(it->se);
ansc[t[p].id]++;
s.erase(it);
}
work(p);
}
up(i,1,n){
cout<<ansc[i]<<" "<<ansl[i]<<endl;
}
return 0;
}
New Year Tree
二进制线段树。
Zbazi in Zeydabad
又是什么奇怪的题目。
题解的 bitset 能艹过去,但是我的 bitset 会 Tle,真是见了鬼。
还是老老实实写树状数组吧。
观察数据范围,应该有一个 \(n^2\) 的做法。
枚举右上角的拐点 \((i,j)\) ,这个点向左最多 \(l\) 个 \(1\),向斜下有 \(d\) 个 \(1\) 有那么所有满足情况底边 \(x\)。
\(i\le x\le i+\min(l,d)\),同时 \(x-l_{x,j}+1\le i\)。
啊这,直接上二维数点就行了。
char c[3005][3005];
int l[3005][3005],ld[3005][3005];
struct BIT {
int t[3005];
int lb(int x) {return x&-x;}
int sum(int x) {
int s=0;
for(int i=x;i;i-=lb(i))
s+=t[i];
return s;
}
int query(int x,int y) {
return sum(y)-sum(x-1);
}
void add(int x,int k) {
for(int i=x;i<=n;i+=lb(i))
t[i]+=k;
}
} t[3005];
struct node {int l,r,x,y,p;} a[9000005],q[9000005];
int cnt1,cnt2;
bool cmp(node a1,node a2) {return a1.p<a2.p;}
signed main() {
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) {
cin>>c[i][j];
if(c[i][j]=='z')
l[i][j]=l[i][j-1]+1;
}
for(int i=n;i>=1;i--)
for(int j=1;j<=m;j++)
if(c[i][j]=='z')
ld[i][j]=ld[i+1][j-1]+1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) {
a[++cnt1]={0,0,i,j,i-l[i][j]+1};
q[++cnt2]={i,i+min(l[i][j],ld[i][j])-1,i,j,i};
}
sort(a+1,a+cnt1+1,cmp);
sort(q+1,q+cnt2+1,cmp);
for(int i=1,j=1;j<=cnt2;j++) {
while(a[i].p<=q[j].p&&i<=cnt1)
t[a[i].y].add(a[i].x,1),i++;
ans+=t[q[j].y].query(q[j].l,q[j].r);
}
cout<<ans;
return 0;
}
Lena and Queries
看起来似乎是进行一个李超树的操作,但是里面有操作删除,所以考虑线段树分治套李超树。
考虑求出一个线段的存在时间。然后在时间轴上建出一棵线段树,然后对线段树上每个要求的节点建立一棵动态开点李超线段树。
int n;
int pos[N];
inline void build(int k,int l,int r){
if(l==r){
pos[l]=k;
return;
}
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
}
int rt[N<<2];
struct node{
int ls,rs,id;
}tr[N<<5];
struct ask{
int op,l,r,k,b;
}q[N];
int tot;
inline int clac(int i,int x){return q[i].k*x+q[i].b;}
inline void insert(int &x,int l,int r,int p){
if(!x) x=++tot;
if(!tr[x].id){
tr[x].id=p;
return;
}
int mid=(l+r)>>1;
if(clac(p,mid)>clac(tr[x].id,mid))swap(p,tr[x].id);
if(clac(p,l)>clac(tr[x].id,l))insert(tr[x].ls,l,mid,p);
if(clac(p,r)>clac(tr[x].id,r))insert(tr[x].rs,mid+1,r,p);
}
inline int ask(int k,int l,int r,int p){
if(!k) return LLONG_MIN;
if(l==r)return clac(tr[k].id,p);
int mid=(l+r)>>1;
return max(p<=mid?ask(tr[k].ls,l,mid,p):ask(tr[k].rs,mid+1,r,p),clac(tr[k].id,p));
}
inline void add(int k,int l,int r,int ql,int qr,int val){
if(l>=ql&&r<=qr){
insert(rt[k],-1e9,1e9,val);
return;
}
int mid=(l+r)>>1;
if(ql<=mid)add(lc,l,mid,ql,qr,val);
if(mid<qr)add(rc,mid+1,r,ql,qr,val);
}
signed main(){
n=read();
build(1,1,n);
up(i,1,n){
q[i].op=read();
if(q[i].op==1)q[i].k=read(),q[i].b=read(),q[i].l=i,q[i].r=n;
else if(q[i].op==2)q[read()].r=i;
else q[i].k=read();
}
up(i,1,n){
if(q[i].op==1)add(1,1,n,q[i].l,q[i].r,i);
}
up(i,1,n){
if(q[i].op==3){
int ans=LLONG_MIN;
for(int x=pos[i];x;x>>=1)ans=max(ans,ask(rt[x],-1e9,1e9,q[i].k));
if(ans==-LLONG_MIN)puts("EMPTY SET");
else write(ans,1);
}
}
return 0;
}
Radio stations
就套上一个三维偏序的板子就行了。
int n,k;
struct node{
int x,r,f,L,R;
}s[N];
vector<int>h;
struct Bit{
inline int lowbit(int x){
return x&(-x);
}
int tr[N];
inline void add(int x,int val){
for(;x<h.size()+100;x+=lowbit(x))tr[x]+=val;
}
inline int ask(int x){
int res=0;
for(;x;x-=lowbit(x))res+=tr[x];
return res;
}
inline void clear(){
memset(tr,0,sizeof tr);
}
}T;
inline int id(int x){
return lower_bound(h.begin(),h.end(),x)-h.begin()+2;
}
inline bool cmpr(node a,node b) {
if(a.r==b.r) return a.f<b.f||a.f==b.f&&a.x<b.x;
return a.r>b.r;
}
inline bool cmpf(node a,node b) {
if(a.f==b.f)return a.r>b.r||a.r==b.r&&a.x<b.x;
return a.f<b.f;
}
int ans;
inline void cdq(int l,int r){
if(l==r)return;
int mid=(l+r)>>1;
cdq(l,mid);cdq(mid+1,r);
sort(s+l,s+mid+1,cmpf);
sort(s+mid+1,s+r+1,cmpf);
T.clear();
int fmin=l,fmax=l,u=mid+1;
while(u<=r){
while(s[u].f-s[fmin].f>k&&fmin<=mid)T.add(s[fmin++].x,-1);
while(s[fmax].f-s[u].f<=k&&fmax<=mid)T.add(s[fmax++].x,1);
ans+=T.ask(s[u].R)-T.ask(s[u].L-1);
u++;
}
}
signed main(){
n=read();k=read();
up(i,1,n){
s[i].x=read();
s[i].r=read();
s[i].f=read();
s[i].L=max(1,s[i].x-s[i].r);
s[i].R=s[i].x+s[i].r;
h.push_back(s[i].R);
h.push_back(s[i].L);
h.push_back(s[i].x);
}
sort(h.begin(),h.end());
h.erase(unique(h.begin(),h.end()),h.end());
up(i,1,n){
s[i].x=id(s[i].x);
s[i].L=id(s[i].L);
s[i].R=id(s[i].R);
}
sort(s+1,s+1+n,cmpr);
cdq(1,n);
cout<<ans;
return 0;
}
Periodic RMQ Problem
考虑暴力动态开点。
注意进行区间覆盖时,左右的区间都要新建,不然 \(push\_up\) 会有问题。
struct Node{int w,ch[2];}a[N<<6];
int f[20][N],pre[N],suc[N],minn,n,nn,q,lg[N],node_cnt,tag[N<<6],rt;
int st_query(int l,int r){
int len=lg[r-l+1];
return min(f[len][l],f[len][r-(1<<len)+1]);
}
void make(){
lg[1]=0;for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
for(int i=1;(1<<i)<=n;i++)
for(int j=1;j+(1<<i)-1<=n;j++)
f[i][j]=min(f[i-1][j],f[i-1][j+(1<<(i-1))]);
pre[1]=f[0][1];for(int i=2;i<=n;i++)pre[i]=min(pre[i-1],f[0][i]);
suc[n]=f[0][n];for(int i=n-1;i>=1;i--)suc[i]=min(suc[i+1],f[0][i]);
minn=pre[n];
}
int query(int l,int r){
if(r-l+1>=n)return minn;
int bl=(l-1)/n+1,br=(r-1)/n+1;
if(bl==br){int L=(bl-1)*n;return st_query(l-L,r-L);}
else{int L=(bl-1)*n,R=(br-1)*n;return min(suc[l-L],pre[r-R]);}
}
void newnode(int &rot,int lt,int rt){
rot=++node_cnt;
a[rot].w=query(lt,rt);
}
void upd(int rot,int w){a[rot].w=tag[rot]=w;}
void pushdown(int rot){
if(tag[rot]){
int t=tag[rot];tag[rot]=0;
if(!a[rot].ch[0])a[rot].ch[0]=++node_cnt;
if(!a[rot].ch[1])a[rot].ch[1]=++node_cnt;
upd(a[rot].ch[0],t),upd(a[rot].ch[1],t);
}
}
void update(int &rot,int lt,int rt,int lq,int rq,int w){
if(!rot)newnode(rot,lt,rt);
if(lt>rq||rt<lq)return;
// cout<<lt<<" "<<rt<<" "<<a[rot].w<<endl;
if(lt>=lq&&rt<=rq){upd(rot,w);return;}
pushdown(rot);int mid=(lt+rt)>>1;
update(a[rot].ch[0],lt,mid,lq,rq,w),update(a[rot].ch[1],mid+1,rt,lq,rq,w);
a[rot].w=min(a[a[rot].ch[0]].w,a[a[rot].ch[1]].w);
}
int query(int &rot,int lt,int rt,int lq,int rq){
if(!rot)newnode(rot,lt,rt);
// cout<<lt<<" "<<rt<<" "<<a[rot].w<<endl;
if(lt>=lq&&rt<=rq)return a[rot].w;
int mid=(lt+rt)>>1;pushdown(rot);
if(rq<=mid)return query(a[rot].ch[0],lt,mid,lq,rq);
else if(lq>mid)return query(a[rot].ch[1],mid+1,rt,lq,rq);
else return min(query(a[rot].ch[0],lt,mid,lq,mid),query(a[rot].ch[1],mid+1,rt,mid+1,rq));
}
int getin(){
int x=0;char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')x=x*10+ch-48,ch=getchar();
return x;
}
int main(){
n=getin(),nn=getin()*n;
for(int i=1;i<=n;i++)f[0][i]=getin();
make();
q=getin();a[0].w=2e9;
for(int i=1;i<=q;i++)
{
int opt=getin(),l=getin(),r=getin(),x;
if(opt==1)x=getin(),update(rt,1,nn,l,r,x);
else printf("%d\n",query(rt,1,nn,l,r));
}
}
MEX Queries
我们需要维护三种操作。
- 把
[l,r]
赋值为 \(1\)。- 把
[l,r]
赋值为 \(0\)。- 把
[l,r]
所有的数取反。
询问出现的第一个 \(0\) 的位置。
看起来就非常的珂朵莉。
struct node{
int l,r;
mutable int v;
inline bool operator<(const node&rhs)const{
return l<rhs.l;
}
};
set<node>odt;
inline auto split(int pos){
auto it=odt.lower_bound({pos,0,0});
if(it!=odt.end()&&it->l==pos)return it;
it--;
int l=it->l,r=it->r,v=it->v;
odt.erase(it);
odt.insert({l,pos-1,v});
return odt.insert({pos,r,v}).fi;
}
inline void assign(int l,int r,int v){
auto ed=split(r+1),bg=split(l);
odt.erase(bg,ed);
odt.insert({l,r,v});
}
inline void rev(int l,int r){
auto ed=split(r+1);
for(auto it=split(l);it!=ed;it++)it->v^=1;
}
inline void ask(){
for(auto it=odt.begin();it!=odt.end();it++){
if(it->v==0){
write(it->l,1);
break;
}
}
}
int n;
signed main(){
odt.insert({1,uinf,0});
n=read();
int op,l,r;
up(i,1,n){
op=read();
if(op==1){
l=read();r=read();
assign(l,r,1);
}
else if(op==2){
l=read();r=read();
assign(l,r,0);
}
else{
l=read();r=read();
rev(l,r);
}
ask();
}
return 0;
}
当然,如果不用珂朵莉树,这道题也可以做,前面三种操作可以看一下这道题:P2572 [SCOI2010] 序列操作。
把他动态开点一下就是了。
Functions On The Segments
可持久化线段树套李超线段树,最后查询就是版本相减。
无他,为码量尔。
int n,m;
int rt[N];
struct node{
int ls,rs;
int suma,sumb;
}tr[N<<5];
int cnt;
inline void change(int &k,int pre,int l,int r,int pos,int vala,int valb){
k=++cnt;
tr[k]=tr[pre];
tr[k].suma+=vala;
tr[k].sumb+=valb;
if(l==r)return;
int mid=(l+r)>>1;
if(mid>=pos)change(tr[k].ls,tr[pre].ls,l,mid,pos,vala,valb);
else change(tr[k].rs,tr[pre].rs,mid+1,r,pos,vala,valb);
}
inline pii operator +(const pii x,const pii y){
return {x.fi+y.fi,x.se+y.se};
}
inline pii operator -(const pii x,const pii y){
return {x.fi-y.fi,x.se-y.se};
}
inline pii ask(int k,int l,int r,int ql,int qr){
if(l>=ql&&r<=qr)return {tr[k].suma,tr[k].sumb};
int mid=(l+r)>>1;
pii ans={0,0};
if(mid>=ql)ans=ans+ask(tr[k].ls,l,mid,ql,qr);
if(mid<qr)ans=ans+ask(tr[k].rs,mid+1,r,ql,qr);
return ans;
}
signed main(){
n=read();
int x1,x2,y1,y2,a,b;
up(i,1,n){
x1=read();x2=read();y1=read();
a=read();b=read();y2=read();
int rt1,rt2;
change(rt1,rt[i-1],0,mod,0,0,y1);
change(rt2,rt1,0,mod,x1+1,a,b-y1);
change(rt[i],rt2,0,mod,x2+1,-a,y2-b);
}
m=read();
int last=0,l,r,k;
pii ans;
up(i,1,m){
ans={0,0};
l=read();r=read();k=(read()+last)%mod;
ans=ans+ask(rt[r],0,mod,0,k);
if(l)ans=ans-ask(rt[l-1],0,mod,0,k);
last=ans.fi*k+ans.se;
write(last,1);
}
return 0;
}
Turn Off The TV
线段树区间加,区间最小值,需要动态开点。
struct node{
int ls,rs,minl,tag;
}tr[N<<5];
int n,m,cnt=1;//一定要赋值成1
inline void push_up(int k){
tr[k].minl=min(tr[tr[k].ls].minl,tr[tr[k].rs].minl);
}
inline void push_down(int k,int l,int r){
if(tr[k].tag){
if(!tr[k].ls)tr[k].ls=++cnt;
if(!tr[k].rs)tr[k].rs=++cnt;
tr[tr[k].ls].tag+=tr[k].tag;
tr[tr[k].rs].tag+=tr[k].tag;
int mid=(l+r)>>1;
tr[tr[k].ls].minl+=tr[k].tag;
tr[tr[k].rs].minl+=tr[k].tag;
tr[k].tag=0;
}
}
inline void change(int &k,int l,int r,int x,int y,int val){
if(!k)k=++cnt;
if(x<=l&&r<=y) {
tr[k].tag+=val;
tr[k].minl+=val;
return;
}
int mid=(l+r)>>1;
push_down(k,l,r);
if(x<=mid)change(tr[k].ls,l,mid,x,y,val);
if(y>mid)change(tr[k].rs,mid+1,r,x,y,val);
push_up(k);
}
inline int ask(int k,int l,int r,int x,int y){
if(!k)return 0;
if(x<=l&&y>=r)return tr[k].minl;
push_down(k,l,r);
int mid=(l+r)>>1,res=inf;
if(x<=mid)res=min(res,ask(tr[k].ls,l,mid,x,y));
if(y>mid)res=min(res,ask(tr[k].rs,mid+1,r,x,y));
return res;
}
int L[N],R[N];
signed main(){
n=read();
int l,r;
up(i,1,n){
L[i]=read();R[i]=read();
int tmp=1;
change(tmp,0,mod,L[i],R[i],1);
}
up(i,1,n){
int tmp=1;
int t=ask(tmp,0,mod,L[i],R[i]);
if(t>=2){
cout<<i;
return 0;
}
}
cout<<-1;
return 0;
}