深度好题

突然想起来自己还不会回滚莫队,于是刷了几道,顺便昨天刚看的换根也练了几道题,这里挑了几个典型的说一下

窝太菜廖,可能觉得是深度好题的原因仅仅是我会打而已

不过起码没贺题解

思路显然是看了的,我不认为我有独立切掉紫题的能力

当然,不是紫题的题显然是没看题解

P1494 小 Z 的袜子

维护每种颜色在 \([L,R]\) 中出现的次数 \(c_i\),答案即为 \(\frac{\sum\limits C^{2}_{c_i}}{C_{r-l+1}^2}\)

莫队,单点增删的时候只需要维护 \(C^2_{c_{a_{i}}}\) 的改变量即可

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
int c[50001];
int cnt[50001];
int len;
int locate[50001];
int ans=0;
struct ques{
    int l,r;
    int id;
    bool operator<(const ques&A)const{
        if(locate[l]==locate[A.l]) return r<A.r;
        return locate[l]<locate[A.l];
    }
}q[50001];
void prework(){
    len=sqrt(n);
    for(int i=1;i<=n;++i){
        locate[i]=i/len;
    }
}
void change(int pos,bool isadd){
    // cout<<"change "<<pos<<" "<<isadd<<endl;
    ans-=max(0ll,cnt[c[pos]]*(cnt[c[pos]]-1));
    cnt[c[pos]]+=(isadd?1:-1);
    ans+=max(0ll,cnt[c[pos]]*(cnt[c[pos]]-1));
    // cout<<"newans: "<<ans<<endl;
}
pair<int,int> answer[50001];
void printanswer(int l,int r,int id){
    int z=ans,m=(r-l+1)*(r-l);
    if(z==0){
        answer[id]={0,1};
        return;
    }
    int g=__gcd(z,m);
    answer[id]={z/g,m/g};
}
signed main(){
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=n;++i){
        cin>>c[i];
    }
    for(int i=1;i<=m;++i){
        cin>>q[i].l>>q[i].r;
        q[i].id=i;
    }
    prework();
    sort(q+1,q+m+1);
    int l=1,r=0;
    for(int i=1;i<=m;++i){
        // cout<<"goal "<<q[i].l<<" "<<q[i].r<<endl;
        while(r<q[i].r) change(++r,true);
        while(l>q[i].l) change(--l,true);
        while(l<q[i].l) change(l++,false);
        while(r>q[i].r) change(r--,false);
        printanswer(q[i].l,q[i].r,q[i].id);
    }
    for(int i=1;i<=m;++i){
        cout<<answer[i].first<<"/"<<answer[i].second<<'\n';
    }
}

JOI sc2014_c 歴史の研究

维护每个值出现的次数,可以发现在增加元素的时候,最大值是很方便更新的,唯一的瓶颈在于删除的时候无法快速找到一个新的最大值

因此回滚莫队

其实这好像是我第一次写回滚莫队,之前甚至不知道怎么回滚

现在会了,钦定一个端点在块内,另一个点保持单调性(不回滚)就行

一开始学的就是 stack 写法(相对于 memcpy 写法而言),感觉应该会快很多

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,Q,len;
int a[100001];
int locate[100001],rb[100001];
struct ques{
    int l,r;
    int id;
    bool operator<(const ques&A)const{
        if(locate[l]==locate[A.l]) return r<A.r;
        return locate[l]<locate[A.l];
    }
}q[100001];
int ans[100001];
inline void prework(){
    len=sqrt(n);
    for(int i=1;i<=n;++i){
        locate[i]=i/len;
        rb[locate[i]]=i;
    }
}
int b[100001];
int cnt[100001];
int vis2[100001];
int cnt2[100001];
int maxn;
int maxn2;
inline void add(int pos){
    // cout<<"add "<<a[pos]<<endl;
    cnt[a[pos]]++;
    maxn=max(maxn,cnt[a[pos]]*b[a[pos]]);
    // for(int i=1;i<=n;++i){
    //     cout<<cnt[i]<<' ';
    // }
    // cout<<endl;
}
int memoryans;
stack<int>st;
signed main(){
    ios::sync_with_stdio(false);
    cin>>n>>Q;
    for(int i=1;i<=n;++i){
        cin>>a[i];
        b[i]=a[i];
    }
    sort(b+1,b+n+1);
    int tmp=unique(b+1,b+n+1)-b-1;
    for(int i=1;i<=n;++i){
        a[i]=lower_bound(b+1,b+tmp+1,a[i])-b;
    }
    for(int i=1;i<=Q;++i){
        cin>>q[i].l>>q[i].r;
        q[i].id=i;
    }
    prework();
    sort(q+1,q+Q+1);
    int lastloc=-1;
    int l,r;
    int backl,backr;
    for(int i=1;i<=Q;++i){
        if(locate[q[i].l]==locate[q[i].r]){
            maxn2=0;
            for(int j=q[i].l;j<=q[i].r;++j){
                if(vis2[a[j]]!=i){
                    vis2[a[j]]=i;
                    cnt2[a[j]]=0;
                }
                cnt2[a[j]]++;
                maxn2=max(maxn2,cnt2[a[j]]*b[a[j]]);
            }
            ans[q[i].id]=maxn2;
        }
        else{
            if(locate[q[i].l]!=lastloc){
                // cout<<"nre locate"<<endl;
                memset(cnt,0,sizeof cnt);
                maxn=0;
                lastloc=locate[q[i].l];
                backl=rb[locate[q[i].l]]+1;
                backr=rb[locate[q[i].l]];
                l=backl;r=backr;
            }
            while(r<q[i].r) add(++r);
            memoryans=maxn;
            while(l>q[i].l){
                add(--l);
                st.push(l);
            }
            ans[q[i].id]=maxn;
            maxn=memoryans;
            l=backl;
            while(!st.empty()){
                cnt[a[st.top()]]--;
                st.pop();
            }
        }
    }
    for(int i=1;i<=Q;++i){
        cout<<ans[i]<<'\n';
    }
}

P8078 秃子酋长

暴力思路是直接上莫队,对值域维护一个 set,插入或删除一个元素的贡献都直接在 set 里查前驱和后继,然后统计它们的距离差

暴力
#include<bits/stdc++.h>
using namespace std;
int n,m,len;
int a[500001];
int locate[500001],l[500001],r[500001];
struct ques{
    int l,r;
    int id;
    bool operator<(const ques&A)const{
        if(locate[l]==locate[A.l]) return r<A.r;
        return l<A.l;
    }
}q[500001];
int pos[500001];
int ans[500001];
set<int>s;
int anss;
void prework(){
    len=sqrt(n);
    memset(l,-1,sizeof l);
    for(int i=1;i<=n;++i){
        locate[i]=i/len;
        if(l[locate[i]]==-1) l[locate[i]]=i;
        r[locate[i]]=i;
    }
}
inline void modify(int __pos,bool isadd){
    if(isadd){
        s.insert(a[__pos]);
        auto iter=s.lower_bound(a[__pos]);
        if(iter!=s.begin() and next(iter)!=s.end()){
            anss+=abs(pos[*prev(iter)]-__pos);
            anss+=abs(pos[*next(iter)]-__pos);
            anss-=abs(pos[*prev(iter)]-pos[*next(iter)]);
        }
        else if(iter!=s.begin()){
            anss+=abs(pos[*prev(iter)]-__pos);
        }
        else if(next(iter)!=s.end()){
            anss+=abs(pos[*next(iter)]-__pos);
        }
    }
    else{
        auto iter=s.lower_bound(a[__pos]);
        if(iter!=s.begin() and next(iter)!=s.end()){
            anss-=abs(pos[*prev(iter)]-__pos);
            anss-=abs(pos[*next(iter)]-__pos);
            anss+=abs(pos[*prev(iter)]-pos[*next(iter)]);
        }
        else if(iter!=s.begin()){
            anss-=abs(pos[*prev(iter)]-__pos);
        }
        else if(next(iter)!=s.end()){
            anss-=abs(pos[*next(iter)]-__pos);
        }
        s.erase(a[__pos]);
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=n;++i){
        cin>>a[i];
        pos[a[i]]=i;
    }
    for(int i=1;i<=m;++i){
        cin>>q[i].l>>q[i].r;
        q[i].id=i;
    }
    prework();
    sort(q+1,q+m+1);
    int l=1,r=0;
    for(int i=1;i<=m;++i){
        while(r<q[i].r) modify(++r,true);
        while(l>q[i].l) modify(--l,true);
        while(r>q[i].r) modify(r--,false);
        while(l<q[i].l) modify(l++,false);
        // for(auto i:s) cout<<i<<' ';
        // cout<<endl;
        ans[q[i].id]=anss;
    }
    for(int i=1;i<=m;++i){
        cout<<ans[i]<<'\n';
    }
}

由于这个 set 复杂度太高,注意到我们用这个 set 主要是为了在值域上查前驱和后继,然而对值域开一个双向链表也能做到这一点

由于链表不支持随机访问,插入元素可能会十分麻烦,复杂度比 set 还高,但是可以很方便地删除,因此想到做只删除的回滚莫队

具体来说,先开一个链表,维护出全局的链表状态和答案,然后跑回滚的时候,左端点固定到块的左端点,右端点跑递减,回滚的时候(这时候你不得不在链表上做增加操作了,否则复杂度又降回 nq 了,但是这里回滚的增加操作比较方便做,因为你不需要统计答案,只需要还原链表的状态,答案可以直接暴力赋值成回滚前记录的答案)

可能有点卡常这个题,不过作为 lxl 出的题还是太松了

#include<bits/stdc++.h>
using namespace std;
template<typename T>
inline void read(T &x){
    int ans=0;char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) ans=ans*10+ch-'0',ch=getchar();
    x=ans;
}
#define int long long
int n,m,len;
int a[500001];
int locate[500001],l[500001],r[500001];
int pos[500001];
int ans[500001];
struct ques{
    int l,r;
    int id;
    inline bool operator<(const ques&A)const{
        if(locate[l]==locate[A.l]) return r>A.r;
        return l<A.l;
    }
}q[500001];
struct hdklist{
    int act_l,act_r;
    int head,tail;
    struct list_t{
        int l,r;
    }ls[500001];
    int ans=0;
    inline void del(int __pos){
        if(__pos!=tail) ans-=abs(pos[ls[__pos].r]-pos[__pos]);
        if(__pos!=head) ans-=abs(pos[ls[__pos].l]-pos[__pos]);
        if(__pos!=tail and __pos!=head) ans+=abs(pos[ls[__pos].l]-pos[ls[__pos].r]);
        if(__pos==tail) tail=ls[__pos].l;
        if(__pos==head) head=ls[__pos].r;
        ls[ls[__pos].l].r=ls[__pos].r;
        ls[ls[__pos].r].l=ls[__pos].l;
    }
    inline void add(int __pos){ //这个东西是个废物函数
        ls[ls[__pos].l].r=__pos;//啥也不管,得从外面记录值然后修改
        ls[ls[__pos].r].l=__pos;//因为这么干复杂度很低所以这么干了
    }
    inline void operator=(const hdklist&A){
        for(int i=1;i<=n;++i){
            ls[i].l=A.ls[i].l;
            ls[i].r=A.ls[i].r;
        }
        head=A.head;
        tail=A.tail;
        act_l=A.act_l;
        act_r=A.act_r;
        ans=A.ans;
    }
};
hdklist t1,t2;
inline void prework(){
    len=sqrt(2*n);
    memset(l,-1,sizeof l);
    for(int i=1;i<=n;++i){
        locate[i]=i/len;
        if(l[locate[i]]==-1) l[locate[i]]=i;
        r[locate[i]]=i;
    }
}
stack<int>st;
signed main(){
    read(n);read(m);
    for(int i=1;i<=n;++i){
        read(a[i]);
        pos[a[i]]=i;
    }
    for(int i=1;i<=m;++i){
        read(q[i].l);read(q[i].r);
        q[i].id=i;
    }
    prework();
    sort(q+1,q+m+1);
    for(int i=1;i<=n;++i){
        if(i!=1) t1.ls[i].l=i-1;
        if(i!=n){
            t1.ls[i].r=i+1;
            t1.ans+=abs(pos[i]-pos[i+1]);
        }
    }
    t1.head=1,t1.tail=n;
    t1.act_l=1,t1.act_r=n;
    int lastloc=-1;
    for(int i=1;i<=m;++i){
        if(locate[q[i].l]!=lastloc){
            lastloc=locate[q[i].l];
            while(t1.act_l<l[locate[q[i].l]]) t1.del(a[t1.act_l++]);
            t2=t1;
        }
        while(t2.act_r>q[i].r) t2.del(a[t2.act_r--]);
        int act_l=t2.act_l,act_r=t2.act_r,anss=t2.ans,head=t2.head,tail=t2.tail;
        while(t2.act_l<q[i].l){
            st.push(a[t2.act_l]);
            t2.del(a[t2.act_l++]);
        }
        ans[q[i].id]=t2.ans;
        while(!st.empty()){
            t2.add(st.top());
            st.pop();
        }
        t2.act_l=act_l;
        t2.act_r=act_r;
        t2.ans=anss;
        t2.head=head;
        t2.tail=tail;
    }
    for(int i=1;i<=m;++i){
        printf("%lld\n",ans[i]);
    }
}

CF916E Jamie and Tree

小杂烩题

迎面而来的第一个问题是换根 LCA

尝试分讨,设当前根为 \(r\)

  • 如果 \(x,y\) 均在 \(r\) 的子树内,答案为 \(\text{lca}(x,y)\)
  • 如果 \(x,y\) 中只有一个在 \(r\) 的子树内,答案为 \(r\)
  • 如果 \(x,y\) 均不在 \(r\) 的子树内
    • \(\text{lca}(x,r)=\text{lca}(y,r)\) 时,\(x,y\)\(r\) 不在当前根节点的同一颗子树内,此时 \(x,y\) 所在子树形态不变,答案仍为 \(\text{lca}(x,y)\)
    • \(\text{lca}(x,r)\neq\text{lca}(y,r)\) 时,\(r\) 一定与某一点位于同一子树内,此时换根后另一点必经过该子树根节点才能到达 \(r\),因此答案为 \(\text{lca}(x,r),\text{lca}(y,r)\) 中较深者

可以发现,分类讨论中出现的公共祖先只有 \(\text{lca}(x,y),\text{lca}(x,r),\text{lca}(y,r)\),因此直接贪心地取深度最大者即可(这三个一定是新树上的合法公共祖先,只是有可能不再是最近的)

迎面而来的第二个问题是换根子树内修改

仍然分讨,设当前根为 \(r\),要修改的子树为 \(x\)

  • \(r=x\),直接修改整颗树(注意这种情况必须单独拉出来考虑,放在下面直接按深度差减一算出负数了)
  • \(r\) 不在 \(x\) 的子树内,换根后形态不变,仍然修改 \(x\) 的子树
  • \(r\)\(x\) 的子树内,此时需要修改的节点是整棵树除去从 \(r\)\(x\) 路径上最靠近 \(x\) 的节点(可以是 \(r\))的子树之后的所有节点,这么说太绕了,但是确实不好表述,建议自行对这种情况画图理解

对于前两种情况,直接树剖后建线段树就行了

对于第三种情况,这个点就相当于是 \((x,r)\) 路径上 \(x\) 点的直属子节点,可以通过类似快速幂的倍增来实现(维护一个 \(fa_{i,x}\)),相当于从 \(r\) 点向上跳 \(deep_r-deep_x-1\)(这就是第一种情况不能分讨进来的原因),情况里的子树减法可以直接转化成贡献相减,修改操作直接对需要减掉的子树做一个负修改就好了

写到这里就做完了,主要是思维难度,码力要求实际上不是很高,没调多久就过了

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,q;
vector<int>e[100001];
int w[100001],wnew[100001];
int fa[20][100001],deep[100001],siz[100001];
int maxson[100001];
int id[100001],idcnt;
int top[100001];
void dfs1(int now,int last){
    // cout<<"dfs1 "<<now<<" "<<last<<endl;
    fa[0][now]=last;
    deep[now]=deep[last]+1;
    siz[now]=1;
    int maxsonsize=0;
    for(int i:e[now]){
        if(i!=last){
            dfs1(i,now);
            siz[now]+=siz[i];
            if(siz[i]>maxsonsize){
                maxsonsize=siz[i];
                maxson[now]=i;
            }
        }
    }
}
void dfs2(int now,int nowtop){
    id[now]=++idcnt;
    wnew[id[now]]=w[now];
    top[now]=nowtop;
    if(!maxson[now]) return;
    dfs2(maxson[now],nowtop);
    for(int i:e[now]){
        if(i!=fa[0][now] and i!=maxson[now]){
            dfs2(i,i);
        }
    }
}
inline int kth_ancestor(int x,int k){
    int base=0;
    // cout<<x<<"- "<<k<<endl;
    while(k){
        if(k&1){
            assert(base<=19);
            x=fa[base][x];
        }
        base++;
        k>>=1;
    }
    return x;
}
inline int lca(int x,int y){
    while(top[x]!=top[y]){
        if(deep[top[x]]<deep[top[y]]) swap(x,y);
        x=fa[0][top[x]];
    }
    return deep[x]<deep[y]?x:y;
}
inline int deep_maxn(int x,int y){
    return deep[x]>deep[y]?x:y;
}
namespace stree{
    struct tree{
        int sum;
        int lazy;
    }t[100001*4];
    #define tol (id*2)
    #define tor (id*2+1)
    #define mid(l,r) mid=((l)+(r))/2
    void build(int id,int l,int r){
        // cout<<"build "<<id<<" "<<l<<" "<<r<<endl;
        assert(l<=r);
        if(l==r){
            t[id].sum=wnew[l];
            return;
        }
        int mid(l,r);
        build(tol,l,mid);
        build(tor,mid+1,r);
        t[id].sum=t[tol].sum+t[tor].sum;
    }
    void pushdown(int id,int l,int r){
        if(t[id].lazy){
            int mid(l,r);
            t[tol].sum+=t[id].lazy*(mid-l+1);
            t[tol].lazy+=t[id].lazy;
            t[tor].sum+=t[id].lazy*(r-(mid+1)+1);
            t[tor].lazy+=t[id].lazy;
            t[id].lazy=0;
        }
    }
    void change(int id,int l,int r,int L,int R,int val){
        if(L<=l and r<=R){
            t[id].sum+=val*(r-l+1);
            t[id].lazy+=val;
            return;
        }
        int mid(l,r);
        pushdown(id,l,r);
        if(R<=mid) change(tol,l,mid,L,R,val);
        else if(L>=mid+1) change(tor,mid+1,r,L,R,val);
        else{
            change(tol,l,mid,L,mid,val);
            change(tor,mid+1,r,mid+1,R,val);
        }
        t[id].sum=t[tol].sum+t[tor].sum;
    }
    int ask(int id,int l,int r,int L,int R){
        // cout<<"ask "<<id<<" "<<l<<" "<<r<<" "<<L<<" "<<R<<endl;
        if(L<=l and r<=R){
            return t[id].sum;
        }
        int mid(l,r);
        pushdown(id,l,r);
        if(R<=mid) return ask(tol,l,mid,L,R);
        else if(L>=mid+1) return ask(tor,mid+1,r,L,R);
        return ask(tol,l,mid,L,mid)+ask(tor,mid+1,r,mid+1,R);
    }
}
inline void change_subtree(int x,int val){
    stree::change(1,1,n,id[x],id[x]+siz[x]-1,val);
}
inline int ask_subtree(int x){
    // cout<<"asksubtree "<<x<<" "<<siz[x]<<endl;
    return stree::ask(1,1,n,id[x],id[x]+siz[x]-1);
}
inline bool isin_subtree(int x,int subroot){
    if(deep[x]<deep[subroot]) return false;
    if(kth_ancestor(x,deep[x]-deep[subroot])==subroot) return true;
    return false;
}
int root;
signed main(){
    // freopen("in.in","r",stdin);
    ios::sync_with_stdio(false);
    cin>>n>>q;
    for(int i=1;i<=n;++i){
        cin>>w[i];
    }
    for(int i=1;i<=n-1;++i){
        int x,y;
        cin>>x>>y;
        e[x].push_back(y);
        e[y].push_back(x);
    }
    dfs1(1,0);
    dfs2(1,1);
    // cout<<"siz: ";
    // for(int i=1;i<=n;++i) cout<<siz[i]<<' ';
    // cout<<endl;
    root=1;
    for(int i=1;i<=19;++i){
        for(int j=1;j<=n;++j){
            fa[i][j]=fa[i-1][fa[i-1][j]];
        }
    }
    stree::build(1,1,n);
    while(q--){
        int opt;cin>>opt;
        if(opt==1){
            cin>>root;
        }
        else if(opt==2){
            int x,y,v;
            cin>>x>>y>>v;
            int __lca=deep_maxn(deep_maxn(lca(x,y),lca(x,root)),lca(y,root));
            if(__lca==root){
                change_subtree(1,v);
            }
            else if(!isin_subtree(root,__lca)){
                change_subtree(__lca,v);
            }
            else{
                change_subtree(1,v);
                change_subtree(kth_ancestor(root,deep[root]-deep[__lca]-1),-v);
            }
        }
        else{
            int x;cin>>x;
            if(x==root){
                cout<<ask_subtree(1)<<'\n';
            }
            else if(!isin_subtree(root,x)){
                cout<<ask_subtree(x)<<'\n';
            }
            else{
                cout<<ask_subtree(1)-ask_subtree(kth_ancestor(root,deep[root]-deep[x]-1))<<'\n';
            }
        }
    }
}
posted @ 2024-11-25 11:06  HaneDaniko  阅读(63)  评论(8编辑  收藏  举报