[SDOI2018]原题识别 题解

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

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

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

Statement

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

Solution

主席树+随机化分析

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

序列问题

操作1

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

\(\sqrt n\) 不是很优雅,考虑 \(log\) 算法

\(pre_i\) 表示 \(i\) 前面第一个和 \(i\) 颜色相同的数的位置

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

可以使用主席树维护,\(\log n\)

操作2

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

暴力的方法是枚举 \(x,y\) ,然后套用操作 \(1\) ,这样一次操作是 \(n^2\log n\)

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

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

  1. \(i\in[1,A]\)

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

注意此时我们并没有算重,因为 \(x,y\) 有序,所以贡献就是 \((i-pre_i)\times(A+B-2i+2)-1\)

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

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

  1. \(i\in(A,B]\)

此时显然要求 \(pre_i<A\)

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

\[(A-pre_i)\times(B-i+1)=A(B+1)-iA-pre_i(B+1)+ipre_i \]

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

每次只要问 \(pre_i<A\) 的四元组前缀和即可

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

随机化分析

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

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

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

树上问题

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

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

操作1

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

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

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

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

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

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

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

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

操作2

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

  1. \(x\in[1,lca],y\in[1,B]\) 序列问题,即此时 \(A^{\prime}=lca\)
  2. \(x\in[1,A],y\in[1,lca]\) 序列问题

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

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

  1. \(x\in[1,lca],y\in[1,lca]\) 序列问题

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

不妨设 \(lca\to \dots \to A\) 为短链

  1. \(x\in(lca,A],y\in(lca,B]\)

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

长链贡献依然好算,所有满足 \(dep[pre_i]<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(\log^2n)\)

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 @ 2021-11-10 17:15  _Famiglistimo  阅读(78)  评论(5编辑  收藏  举报