[SDOI2018]原题识别 题解

参考:题解 P4618 SDOI2018原题识别 - shadowice1984 的博客 - 洛谷博客 (luogu.com.cn)

顺便说一句:注意到上面那位大佬代码中主席树写法,因为递归的时候是 (mid,r) 作为右区间,所以判断叶子的条件变成了 l==r1 ,而相应地也应该给主席树加一个叶子 n+1 ,避免遍历不到 n 号叶子节点

(因为这个没看懂这个点在电脑前死磕了好久 /kel)

Statement

[P4618 SDOI2018]原题识别 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

Solution

主席树+随机化分析

发现 subtask2 的意思是链,那么不妨从链开始考虑

序列问题

操作1

经典问题数颜色,我们可以考虑使用莫队解决

n 不是很优雅,考虑 log 算法

prei 表示 i 前面第一个和 i 颜色相同的数的位置

那么对于一个区间 [l,r] ,其答案就是区间中 prei<l 的点的个数

可以使用主席树维护,logn

操作2

问题变成了在 [1,A] 中选一个点 x[1,B] 中选一个点 y

暴力的方法是枚举 x,y ,然后套用操作 1 ,这样一次操作是 n2logn

发现过程中有非常多的重复计算,也就是对于一个点 i 而言,有多个询问反复在问它是否满足 prei<x

知道期望线性,我们考虑计算点 i 对多少个询问有贡献,分类讨论一下

  1. i[1,A]

此时,对于询问 x(prei,i],y[i,B] ,以及询问 x[i,A],y(prei,i] ,都是有贡献的。

注意此时我们并没有算重,因为 x,y 有序,所以贡献就是 (iprei)×(A+B2i+2)1

等等,为什么要减 1 ? 发现 (i,i) 算重了。所以还是算重了

把贡献拆开 (iprei)×(2i+2)1+(iprei)×(A+B) ,我们可以考虑做一下 (iprei)×(2i+2)1iprei 的前缀和,于是可以 O(1) 回答

  1. i(A,B]

此时显然要求 prei<A

那么,对于询问 x(prei,A],y[i,B] 都是可以的,所以贡献就是

(Aprei)×(Bi+1)=A(B+1)iAprei(B+1)+iprei

考虑使用主席树维护四元组 (1,i,prei,iprei) 的前缀和即可,O(log) 回答

每次只要问 prei<A 的四元组前缀和即可

到此为止,我们做到了 log 进行一个操作

随机化分析

观察题目给的数据生成方式:

  • 给定从 1p 的一条链,这里暂且称为“主链”,然后剩下的点随机和这个主链连边 两个点的 lca 和这两个点的期望深度差 logn
  • 每一个点的颜色随机,颜色范围 [1,n] 每一种颜色的期望个数 O(1)

我们得到了这样的性质,考虑序列上树

树上问题

我们首先根据这颗树建立起维护刚刚那玩意的主席树,即在父亲的版本下新增儿子的点

由于现在是在树上,所以深度对应成了下标,维护的四元组变成了 (1,dep[i],dep[pre[i]],dep[i]×dep[pre[i]])

操作1

我们现在的思路是,通过主席树直接得到长链贡献,暴力加入短链考虑是否有贡献

长链贡献显然很好做,和序列问题是一样的

考虑对于短链上一个点 i ,它有贡献当且仅当:dep[pre[i]]<dep[lca] and j[lca,B],a[j]a[i]

第二个条件像这样转化:设点 k 是再链 [1,B] 最后出现的颜色 a[i] ,那么 dep[k]<dep[lca]

如何维护这个点 k ?即维护一条从根开始的链上某个颜色出现的最后位置?

考虑在树上建一个可持久化数组,每经过一个树上点 u,便将数组下标为 ai的位置赋值为 i

那么我们在某一个点 u 的历史版本对应的就是 u1 路径上某个颜色最后一次出现的位置

所以我们做到了 logn 查询最后位置,知道短链上期望只有 O(logn) 个点,所以我们操作 1 的复杂度是 O(log2n)

操作2

先解决一些简单的情况,注意这里仍然是以贡献的角度

  1. x[1,lca],y[1,B] 序列问题,即此时 A=lca
  2. x[1,A],y[1,lca] 序列问题

那么这里会不会出现算重的情况?答案是,是的

所以我们需要减掉下面这一种情况的贡献:

  1. x[1,lca],y[1,lca] 序列问题

不妨把距离 lca 更近的点到 lca(不含 lca) 的链称为短链,对应的称为长链

不妨设 lcaA 为短链

  1. x(lca,A],y(lca,B]

按照操作 1 的思路,首先我们考虑长链的贡献,再暴力插入短链

长链贡献依然好算,所有满足 dep[prei]<dep[lca] 的点 i(dep[u]dep[lca])(dep[v]dep[i]+1) 主席树查一下就可以了

对于短链上的一个点 i ,设颜色 a[i] 在 从根出发到长链上 第一个深度大于 lca 的位置叫做 pr ,贡献即为 (dep[pr]dep[lca]1)(dep[u]dep[i]+1)

假如 pr 不存在,也就是说其他颜色为 a[i] 的点都在 lca 的祖先,贡献为 (dep[v]dep[lca])(dep[u]dep[i]+1)

但是我们的可持久化数组维护的是最后一个位置呀,怎么找 pr 哦?

由于 每一种颜色的期望个数 O(1) ,所以我们可以先找到最后一个,暴力跳即可,暴力跳期望复杂度 O(1)

注意到 lca 的贡献我们还没有算,最后加上 (dep[v]dep[lca])(dep[u]dep[lca]) 即可

操作二复杂度 O(log2n)

Code

//Just follow your heart.
//Never give in.
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5+5;

int read(){
    int s=0,w=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
    return s*w;
}

struct data{
    int v0; ll v1,v2,v3;
    data(int v0=0,ll v1=0,ll v2=0,ll v3=0):v0(v0),v1(v1),v2(v2),v3(v3){}
    data operator+(const data&rhs)const{return data(v0+rhs.v0,v1+rhs.v1,v2+rhs.v2,v3+rhs.v3);}
    data operator-(const data&rhs)const{return data(v0-rhs.v0,v1-rhs.v1,v2-rhs.v2,v3-rhs.v3);}
};
struct node{
    ll v1,v2;
    node(ll v1=0,ll v2=0):v1(v1),v2(v2){}
    node operator+(const node&rhs)const{return node(v1+rhs.v1,v2+rhs.v2);}
    node operator-(const node&rhs)const{return node(v1-rhs.v1,v2-rhs.v2);}
}val[N];
struct Edge{
    int nex,to;
}edge[N<<1];
int head[N],pre[N],dep[N],a[N],f[N];
int T,n,m,p,elen;
bool vis[N];
stack<int>stk[N];

struct pre_tree{
    data t[N*20];
    int ls[N*20],rs[N*20],rot[N],siz;
    void clear(){siz=1;}
    void insert(int l,int r,int &p,int q,int id,data ad){
        t[p=++siz]=t[q]+ad,ls[p]=ls[q],rs[p]=rs[q];
        if(l==r)return ls[p]=rs[p]=0,void(); int mid=(l+r)>>1;
        id<=mid?insert(l,mid,ls[p],ls[q],id,ad):insert(mid+1,r,rs[p],rs[q],id,ad);
    }
    data query(int l,int r,int p,int L,int R){
        if(!p||L<=l&&r<=R)return t[p];
        int mid=(l+r)>>1; data res;
        if(L<=mid)res=res+query(l,mid,ls[p],L,R);
        if(mid<R)res=res+query(mid+1,r,rs[p],L,R);
        return res;
    }
    void cinsert(int p,int q,int id,data ad){insert(0,n,rot[p],rot[q],id,ad);}
    data cquery(int p,int q,int l,int r){return query(0,n,rot[q],l,r)-query(0,n,rot[p],l,r);}
}pt;
struct pre_array{
    int t[N*20],ls[N*20],rs[N*20],rot[N],siz;
    void clear(){siz=1;}
    void insert(int l,int r,int &p,int q,int id,int ad){
        t[p=++siz]=ad,ls[p]=ls[q],rs[p]=rs[q];
        if(l==r)return ls[p]=rs[p]=0,void(); int mid=(l+r)>>1;
        id<=mid?insert(l,mid,ls[p],ls[q],id,ad):insert(mid+1,r,rs[p],rs[q],id,ad);
    }
    int query(int l,int r,int p,int id){
        if(!p||l==r)return t[p]; int mid=(l+r)>>1;
        return id<=mid?query(l,mid,ls[p],id):query(mid+1,r,rs[p],id);
    }
    void cinsert(int p,int q,int id,int ad){insert(0,n,rot[p],rot[q],id,ad);};
    int cquery(int p,int id){return query(0,n,rot[p],id);}
}pa;

void addedge(int u,int v){edge[++elen]={head[u],v},head[u]=elen;}
unsigned int SA, SB, SC;
unsigned int rng61(){
	SA^=SA<<16; SA^=SA>>5; SA^=SA<<1;
	unsigned int t=SA;
	SA=SB; SB=SC; SC^=t^SA;
	return SC;
}
void gen(){
	scanf("%d%d%u%u%u",&n,&p,&SA,&SB,&SC);
	for(int i=2;i<=p;i++)addedge(i-1,i);
	for(int i=p+1;i<=n;i++)addedge(rng61()%(i-1)+1,i);
	for(int i=1;i<=n;i++)a[i]=rng61()%n+1;
}

void dfs(int u){
    pre[u]=stk[a[u]].empty()?0:stk[a[u]].top(),stk[a[u]].push(u);
    pt.cinsert(u,f[u],dep[pre[u]],data(1,dep[u],dep[pre[u]],(ll)dep[u]*dep[pre[u]]));
    pa.cinsert(u,f[u],a[u],u);
    val[u]=val[f[u]]+node(dep[u]-dep[pre[u]],(ll)(dep[u]-dep[pre[u]])*(-2*dep[u]+2)-1);
    for(int e=head[u],v;v=edge[e].to,e;e=edge[e].nex)dep[v]=dep[f[v]=u]+1,dfs(v);
    stk[a[u]].pop();
}
int query1(int u,int v){
    if(dep[u]>dep[v])swap(u,v);
    int lca=u,tmp=v;
    for(;tmp>p;tmp=f[tmp])vis[tmp]=true;
    for(;lca>p;lca=f[lca])if(vis[lca])goto ed1;
    lca=(dep[lca]<dep[tmp])?lca:tmp; ed1:;
    for(tmp=v;tmp>p;tmp=f[tmp])vis[tmp]=false;
    int res=pt.cquery(f[lca],v,0,dep[lca]-1).v0;
    for(int x=u;x^lca;x=f[x])
        if(dep[pre[x]]<dep[lca]&&dep[pa.cquery(v,a[x])]<dep[lca])res++;
    return res;
}
ll subquery(int l,int r){
    ll L=dep[l],R=dep[r];
    data res=(l!=r)?pt.cquery(l,r,0,dep[l]):data();
    return val[l].v1*(L+R)+val[l].v2+res.v0*L*(R+1)-res.v1*L-res.v2*(R+1)+res.v3;
}
ll query2(int u,int v){
    if(dep[u]>dep[v])swap(u,v);
    int lca=u,tmp=v;
    for(;tmp>p;tmp=f[tmp])vis[tmp]=true;
    for(;lca>p;lca=f[lca])if(vis[lca])goto ed2;
    lca=(dep[lca]<dep[tmp])?lca:tmp; ed2:;
    for(tmp=v;tmp>p;tmp=f[tmp])vis[tmp]=false;
    ll res=subquery(lca,u)+subquery(lca,v)-subquery(lca,lca);
    if(lca==u)return res;
    data tr=pt.cquery(lca,v,0,dep[lca]-1);
    res+=(dep[u]-dep[lca])*((ll)tr.v0*(dep[v]+1)-tr.v1)+(ll)(dep[v]-dep[lca])*(dep[u]-dep[lca]);
    for(int x=u;x^lca;x=f[x]){
        if(dep[pre[x]]>=dep[lca])continue;
        int pr=pa.cquery(v,a[x]);
        if(dep[pr]<dep[lca]){
            res+=(ll)(dep[v]-dep[lca])*(dep[u]-dep[x]+1);
            continue;
        }
        for(;dep[pre[pr]]>=dep[lca];pr=pre[pr]);
        if(dep[pr]==dep[lca])continue;
        res+=(ll)(dep[pr]-dep[lca]-1)*(dep[u]-dep[x]+1);
    }
    return res;
}

signed main(){
    T=read();
    while(T--){
        gen(),m=read(),dfs(dep[1]=1);
        for(int i=1,op,l,r;i<=m;++i)
            op=read(),l=read(),r=read(),
            printf("%lld\n",(op==1)?query1(l,r):query2(l,r));
        pt.clear(),pa.clear(),elen=0;
        memset(head,0,sizeof(head));
    }
    return 0;
}
posted @   _Famiglistimo  阅读(108)  评论(5编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示