2021牛客暑期多校训练营7 部分题题解

B.xay loves monotonicity

题目链接

xay loves monotonicity

简要题解

不难看出这是一道维护序列信息的数据结构题。
我们需要支持单点修改\(A\)序列,区间修改\(B\)序列,以及区间查询一个特殊的值。
具体地说,对于一个询问,我们要取出序列\(A\)在这个区间内,包含左端点的最长不降子序列,并把子序列的位置对应到序列\(B\)中得到一个\(0/1\)串。
我们需要求这个\(0/1\)串中有多少个值不同的相邻对。

这可以说是一个经典题,应该也可以说是一个套路题。
如果我们要在一个区间内求一个最长不降子序列,那么这个区间内的最大值一定在子序列中,并且这个最大值一定在序列末尾。
基于这个性质,两个区间的信息就可以合并,而线段树很适合维护多个区间合并的信息。

我们在线段树上维护区间内\(A\)序列的最大值,以及这个最大值所在位置对应的\(B\),若有多个最大值则维护最右边的那一个。
我们需要解决的问题可以用一个\(Calc(l,r,a,b)\)来描述:如果当前序列末尾的数为\(a\),其对应的\(B\)值为\(b\),那么\([l,r]\)这段区间可以提供多少贡献。
很明显,加上这段区间后,序列的末尾,要么是这段区间的最大值,要么是原来的\(a\),于是我们可以接着往后面合并区间。
线段树可以将一个询问区间分成\(logn\)个线段树节点维护的区间,我们已经知道如何合并区间,现在只需要对每个区间快速计算\(Calc(l,r,a,b)\)
对于线段树节点维护的一个区间来说,如果左边\(A\)序列的最大值小于\(a\),那么左边就没有贡献,直接递归右边。
否则,合并左半边区间后,不降子序列的末尾就是左半边序列的最大值,设为\(a_l\)
而我们已经知道\(a_l\)以及对应的\(b_l\),只要提前算出右半边区间的\(Calc(l,r,a_l,b_l)\),记为\(C\),就可以直接加上贡献,只递归左边。
我们从下往上维护每个区间的\(C\),每次维护需要\(logn\)的递归计算,因此总复杂度是\(O(nlog^2n)\)的。

因此,我们需要在线段树上维护三个值:\(A\)序列的区间最大值\(a\)\(a\)\(B\)序列对应位置的值\(b\),当左区间最大值作为子序列末尾时右区间的\(C\)值。
对于\(A\)序列和\(B\)序列的修改,就是单点修改与区间修改,修改完及时\(Push\_up\)维护\(C\)值即可。
对于一个区间的询问,在线段树上可以拆成\(logn\)个区间,依次对每个区间调用\(Calc\)函数,再合并即可。
代码如下:

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+10;
int n,Qs,A[MAXN],B[MAXN];
int Read()
{   int a=0,c=1;   char b=getchar();
    while(b!='-'&&(b<'0'||b>'9')) b=getchar();
    if(b=='-') c=-1,b=getchar();
    while(b>='0'&&b<='9') a=a*10+b-48,b=getchar();
    return a*c;
}
int Max(int A,int B){   return A>B?A:B;   }
namespace TREE
{   struct ELE{   int Mv,Ma,Mb;   };
    int Maxa[MAXN*4],Maxl[MAXN*4];
    bool Maxb[MAXN*4],Lan[MAXN*4];
    ELE operator + (ELE A,ELE B)
    {   if(B.Ma>=A.Ma) return (ELE){A.Mv+B.Mv,B.Ma,B.Mb};
        return (ELE){A.Mv+B.Mv,A.Ma,A.Mb};
    }
    void Push_down(int S)
    {   if(!Lan[S]) return;
        Maxb[S<<1]^=1,Lan[S<<1]^=1,Maxb[S<<1|1]^=1,Lan[S<<1|1]^=1,Lan[S]=0;
    }
    int Calc(int S,int Le,int Ri,int Num,int Nb,int Mid=0)
    {   if(Le==Ri) return Maxa[S]>=Num?Maxb[S]!=Nb:0;
        Mid=(Le+Ri)>>1,Push_down(S);
        return Maxa[S<<1]>=Num?Calc(S<<1,Le,Mid,Num,Nb)+Maxl[S]:Calc(S<<1|1,Mid+1,Ri,Num,Nb);
    }
    void Push_up(int S,int Le,int Ri,int Mid=0)
    {   if(Maxa[S<<1|1]<Maxa[S<<1]) Maxa[S]=Maxa[S<<1],Maxb[S]=Maxb[S<<1];
        else Maxa[S]=Maxa[S<<1|1],Maxb[S]=Maxb[S<<1|1];
        Mid=(Le+Ri)/2,Maxl[S]=Calc(S<<1|1,Mid+1,Ri,Maxa[S<<1],Maxb[S<<1]);
    }
    void Build(int S,int Le,int Ri,int Mid=0)
    {   if(Le==Ri) return Maxa[S]=A[Le],Maxb[S]=B[Le],(void)0;
        Mid=(Le+Ri)>>1,Build(S<<1,Le,Mid),Build(S<<1|1,Mid+1,Ri),Push_up(S,Le,Ri);
    }
    void Modify1(int S,int Le,int Ri,int Aim,int Num,int Mid=0)
    {   if(Le==Ri) return Maxa[S]=Num,(void)0;
        Mid=(Le+Ri)>>1,Push_down(S);
        Aim<=Mid?Modify1(S<<1,Le,Mid,Aim,Num):Modify1(S<<1|1,Mid+1,Ri,Aim,Num),Push_up(S,Le,Ri);
    }
    void Modify2(int S,int Le,int Ri,int Al,int Ar,int Mid=0)
    {   if(Al<=Le&&Ri<=Ar) return Lan[S]^=1,Maxb[S]^=1,(void)0;
        Mid=(Le+Ri)/2,Push_down(S);
        if(Al<=Mid) Modify2(S<<1,Le,Mid,Al,Ar),Push_up(S,Le,Ri);
        if(Mid<Ar) Modify2(S<<1|1,Mid+1,Ri,Al,Ar),Push_up(S,Le,Ri);
        Push_up(S,Le,Ri);
    }
    ELE Query(int S,int Le,int Ri,int Al,int Ar,int Num,int Nb,int Mid=0)
    {   if(Al<=Le&&Ri<=Ar)
        {   if(Maxa[S]<Num) return (ELE){0,Num,Nb};
            return (ELE){Calc(S,Le,Ri,Num,Nb),Maxa[S],Maxb[S]};
        }
        Mid=(Le+Ri)>>1,Push_down(S);
        ELE Ra=(ELE){0,Num,Nb},Rb=(ELE){0,Num,Nb};
        if(Al<=Mid) Ra=Query(S<<1,Le,Mid,Al,Ar,Num,Nb),Rb=Ra,Rb.Mv=0;
        if(Mid<Ar) Rb=Query(S<<1|1,Mid+1,Ri,Al,Ar,Ra.Ma,Ra.Mb);
        return Ra+Rb;
    }
    int Query2(int S,int Le,int Ri,int Aim,int Mid=0)
    {   if(Le==Ri) return Maxb[S];
        return Push_down(S),Mid=(Le+Ri)/2,Aim<=Mid?Query2(S<<1,Le,Mid,Aim):Query2(S<<1|1,Mid+1,Ri,Aim);
    }
}using namespace TREE;
int main()
{   n=Read();
    for(int i=1;i<=n;i++) A[i]=Read();
    for(int i=1;i<=n;i++) B[i]=Read();
    Build(1,1,n),Qs=Read();
    for(int i=1,K,T1,T2;i<=Qs;i++)
    {   K=Read(),T1=Read(),T2=Read();
        if(K==1) Modify1(1,1,n,T1,T2),A[T1]=T2;
        if(K==2) Modify2(1,1,n,T1,T2);
        if(K==3) printf("%d\n",Query(1,1,n,T1,T2,A[T1],Query2(1,1,n,T1)).Mv);
    }
}

J.xay loves Floyd

题目链接

xay loves Floyd

简要题解

对于正确的\(dis\)数组,我们可以直接跑\(n\)\(Dijkstra\),在大概\(O(n*mlogn)\)的时间内求出来。
对于不正确的\(dis\)数组,假设我们现在转移\(dis[i][j]\),要判断\(dis[i][j]\)是否正确。
什么时候\(dis[i][j]\)是对的呢?存在某个点\(k\),使得\(dis[i][k]\)\(dis[k][j]\)是对的,且\(k\)\(x\)\(y\)的某一条最短路上。
由于我们只需要知道是否正确,而不需要知道具体的值,那么为了加速转移,可以用\(bitset\)来维护。

具体地说,我们设三个\(bitset\)数组,\(F[i][j]\)表示\(dis[i][j]\)是否正确,\(G[i][j]\)表示\(dis[j][i]\)是否正确。
为了判断\(k\)是否在\(i\)\(j\)的最短路上,我们每次枚举到一个\(i\),就处理一个\(Pot[j][k]\),表示\(k\)是否在\(i\)\(j\)的最短路上。
如果我们有了这三个数组,对于枚举到的\(i\)\(j\)来说,只要\(F[i]\)\(G[j]\)\(Pot[j]\)三个集合有交,那么\(dis[i][j]\)就是正确的。

\(F[i][j]\)\(G[i][j]\)很好维护,设置好初始状态,之后在转移时跟着更新即可。
初始状态是什么?我们之后的转移都需要依赖中间点\(k\),因此初始状态要加入没有中间点的情况,也就是两点直接连边为最短路的情况。
现在考虑如何求\(Pot[j][k]\)
我们枚举到一个\(i\)之后,就把所有点按照到\(i\)的最短路从小到大排序,设这个序列为\(s\),那么若\(k<j\),则\(dis[i][s_k]<dis[i][s_j]\)
由于边权为正,\(Pot[s_k][s_j]\)就一定是\(0\),换句话说,在序列\(s\)中,只有前面的点才可能成为后面的点到\(i\)的最短路中间点。
那么我们从前往后处理\(Pot[s_j]\),枚举连接\(s_j\)的每一条边,设边的另一端点为\(s_k\),若\(dis[i][s_j]=dis[i][s_k]+w\),则将\(Pot[j]\)或上\(Pot[k]\)
因为\((s_k,s_j)\)这条边在最短路上,那么所有在\(s_k\)最短路上的点,都会在\(s_j\)的最短路上。

我们实际上只是用\(bitset\)加速了错误做法的过程,本质上还是在模拟。
注意\(Pot[i][j]\)是正确的,也就是上帝视角,\(F[i][j]\)\(G[i][j]\)是我们在模拟错误做法时维护的。
时间复杂度\(O(n*mlogn+\frac{n^2m}{\omega})\)
代码如下:

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int MAXN=2010;
const int Inf=1e9;
struct EDGE{   int u,v,w,Next;   }Edge[MAXN*4];
int n,m,Es,Ns,First[MAXN],Dis[MAXN][MAXN];
int Ans,Ls,Seq[MAXN],Vis[MAXN];
bitset<MAXN>F[MAXN],G[MAXN],Pot[MAXN];
void Link(int u,int v,int w){   Edge[++Es]=(EDGE){u,v,w,First[u]},First[u]=Es;   }
namespace DIJ
{   struct ELE{   int Np,Nd;   };
    bool operator < (ELE A,ELE B){   return A.Nd==B.Nd?A.Np<B.Np:A.Nd>B.Nd;   }
    priority_queue<ELE>Heap;
    void Dijkstra(int St,int *Dis)
    {   for(int i=1;i<=n;i++) Dis[i]=Inf;
        Dis[St]=0,Heap.push((ELE){St,0});
        for(int Now;!Heap.empty();)
        {   Now=Heap.top().Np,Heap.pop(),Vis[Now]=0;
            for(int i=First[Now],v,w;i!=-1;i=Edge[i].Next)
            {   v=Edge[i].v,w=Edge[i].w;
                if(Dis[v]<=Dis[Now]+w) continue ;
                Dis[v]=Dis[Now]+w;
                if(!Vis[v]) Vis[v]=1,Heap.push((ELE){v,Dis[v]});
            }
        }
    }
}using namespace DIJ;
bool cmp(int A,int B){   return Dis[Ns][A]<Dis[Ns][B];   }
int main()
{   scanf("%d%d",&n,&m);
    memset(First,-1,sizeof(First));
    for(int i=1,u,v,w;i<=m;i++) scanf("%d%d%d",&u,&v,&w),Link(u,v,w);
    for(int i=1;i<=n;i++) Dijkstra(i,Dis[i]),F[i].set(i),G[i].set(i);
    for(int i=1,u,v,w;i<=Es;i++)
    {   u=Edge[i].u,v=Edge[i].v,w=Edge[i].w;
        if(Dis[u][v]==w) F[u].set(v),G[v].set(u);
    }
    for(int i=1;i<=n;i++)
    {   for(int j=1;j<=n;j++) Seq[j]=j,Pot[j].reset(),Pot[j].set(j);
        Ns=i,sort(Seq+1,Seq+n+1,cmp);
        for(int j=1;j<=n;j++)
            for(int k=First[Seq[j]],v,w;k!=-1;k=Edge[k].Next)
            {   v=Edge[k].v,w=Edge[k].w;
                if(Dis[i][v]==Dis[i][Seq[j]]+w) Pot[v]|=Pot[Seq[j]];
            }
        for(int j=1;j<=n;j++)
            if((F[i]&G[j]&Pot[j]).any()) F[i].set(j),G[j].set(i);
    }
    for(int i=1;i<=n;i++) Ans+=F[i].count();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++) Ans+=(Dis[i][j]==Inf);
    printf("%d\n",Ans);
}

K.xay loves sequence

题目链接

xay loves sequence

简要题解

我们每次的操作都是给一个区间加一或减一,那么很重要的一个转化就是差分
我们取出询问的区间,重新从\(1\)编号,构造一个长为\(n+1\)的序列{\(b_i\)},满足\(b_i=a_i-a_{i-1}\),默认\(a_0=a_{n+1}=0\)
如果不模\(k\),那么我们的任务变成了:每次操作可以在{\(b_i\)}中选一个位置加一,再选一个位置减一,求将{\(b_i\)}全消成\(0\)的最小操作次数。
这个最小操作次数很好算,就是\(\frac{1}{2}\sum_{i=1}^{n+1}|b_i|\),因为是差分序列,\(\sum_{i=1}^{n+1}b_i=0\),所以一定可以构造出最优方案,每次使\(\sum_{i=1}^{n+1}|b_i|\)减二。

现在考虑模\(k\),那么在差分数组上体现为一个新操作:在{\(b_i\)}上选一个位置加\(k\),再选一个位置减\(k\)。并且这个操作不计入答案。
如果我们要对某个\(b_i\)进行新操作,那么当\(b_i>0\)时,我们一定减\(k\),否则不优。同理,当\(b_i<0\)时,我们一定加\(k\)
对于\(b_i=0\)的地方我们不需要动,因为动了肯定不会使答案更优。
\(b_i>0\)时,修改后,贡献由\(|b_i|\)变成了\(|b_i-k|\),即\(\Delta=k-2*b_i\),我们设\(c_i=k-2*b_i\)
\(b_i<0\)时,修改后,贡献由\(|b_i|\)变成了\(|b_i+k|\),即\(\Delta=k+2*b_i\),我们设\(d_i=k+2*b_i\)
为了使答案最优,新操作转化成了:在\(c_i\)\(d_i\)中各选\(m\)个位置,使得选出来的元素之和最小,其中\(m\)是任意非负整数。
\(c_i\)\(d_i\)排序之后,我们设\(e_i=c_i+d_i\),那么我们只需要取出所有\(e_i<0\)的元素即可,并且这些\(e_i\)一定在前\(m\)个位置,可以通过二分找到\(m\)

现在考虑多个询问,并且询问的是一个区间,该如何处理。
排序的过程我们可以直接用桶实现,区间的处理也可以用主席树来维护。
具体地说,我们建一棵值域主席树,分别维护\(c_i\)\(d_i\),那么对于一个询问来说,我们主席树作差可以取出区间内的\(c_i\)\(d_i\)
注意我们取出区间的\(c_i\)\(d_i\)时,第一个数和最后一个数的值会有变化,我们可以先进行单点修改,求出答案之后复原即可。
然后我们二分\(m\),再在主席树上进行类似求区间第\(K\)大的操作来\(Check\),确定\(m\)之后查询区间前\(m\)大就可以计算出总共的\(\Delta\)
那么该询问的答案就是\(\frac{1}{2}(\sum_{i=1}^{n+1}|b_i|-\Delta)\)
时间复杂度\(O(n*log^2n)\)
代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2e5+10;
const int Inf=2147483647;
struct ELE{   int L,R,K,Id;   };
int n,Qs,A[MAXN],B[MAXN];
ll Sum1,Ns,Sum[MAXN],Ans[MAXN];
vector<ELE>Q[MAXN];
int Read()
{   int a=0,c=1;   char b=getchar();
    while(b!='-'&&(b<'0'||b>'9')) b=getchar();
    if(b=='-') c=-1,b=getchar();
    while(b>='0'&&b<='9') a=a*10+b-48,b=getchar();
    return a*c;
}
ll Abs(ll S){   return S>0?S:-S;   }
int Min(int A,int B){   return A<B?A:B;   }
namespace TREE
{   struct TR{   int C1,C2,Lson,Rson;   ll S1,S2;   }Tr[MAXN*40];
    int Ts,Root[MAXN];
    void Push_up(int S)
    {   Tr[S].C1=Tr[Tr[S].Lson].C1+Tr[Tr[S].Rson].C1;
        Tr[S].C2=Tr[Tr[S].Lson].C2+Tr[Tr[S].Rson].C2;
        Tr[S].S1=Tr[Tr[S].Lson].S1+Tr[Tr[S].Rson].S1;
        Tr[S].S2=Tr[Tr[S].Lson].S2+Tr[Tr[S].Rson].S2;
    }
    void Modify(int &S,int Le,int Ri,int D,int Aim,int K)
    {   Tr[++Ts]=Tr[S],S=Ts;
        if(Le==Ri) return K==1?(Tr[S].C1+=D,Tr[S].S1+=Le*D):(Tr[S].C2+=D,Tr[S].S2+=Le*D),(void)0;
        int Mid=(1ll*Le+Ri)>>1;
        return Aim<=Mid?Modify(Tr[S].Lson,Le,Mid,D,Aim,K):Modify(Tr[S].Rson,Mid+1,Ri,D,Aim,K),Push_up(S);
    }
    ll Query1(int Sl,int Sr,int Le,int Ri,int K)
    {   if(Le==Ri) return 1ll*Le*Min(K,Tr[Sr].C1-Tr[Sl].C1);
        if(Tr[Sr].C1-Tr[Sl].C1<=K) return Tr[Sr].S1-Tr[Sl].S1;
        int Mid=(1ll*Le+Ri)>>1,Ll=Tr[Sl].Lson,Lr=Tr[Sl].Rson,Rl=Tr[Sr].Lson,Rr=Tr[Sr].Rson;
        if(Tr[Rr].C1-Tr[Lr].C1>=K) return Query1(Lr,Rr,Mid+1,Ri,K);
        return Query1(Ll,Rl,Le,Mid,K-Tr[Rr].C1+Tr[Lr].C1)+Tr[Rr].S1-Tr[Lr].S1;
    }
    ll Query2(int Sl,int Sr,int Le,int Ri,int K)
    {   if(Le==Ri) return 1ll*Le*Min(K,Tr[Sr].C2-Tr[Sl].C2);
        if(Tr[Sr].C2-Tr[Sl].C2<=K) return Tr[Sr].S2-Tr[Sl].S2;
        int Mid=(1ll*Le+Ri)>>1,Ll=Tr[Sl].Lson,Lr=Tr[Sl].Rson,Rl=Tr[Sr].Lson,Rr=Tr[Sr].Rson;
        if(Tr[Rr].C2-Tr[Lr].C2>=K) return Query2(Lr,Rr,Mid+1,Ri,K);
        return Query2(Ll,Rl,Le,Mid,K-Tr[Rr].C2+Tr[Lr].C2)+Tr[Rr].S2-Tr[Lr].S2;
    }
    ll Query3(int Sl,int Sr,int Le,int Ri,int K)
    {   if(Le==Ri) return K<=Tr[Sr].C1-Tr[Sl].C1?Le:-Inf;
        int Mid=(1ll*Le+Ri)>>1,Ll=Tr[Sl].Lson,Lr=Tr[Sl].Rson,Rl=Tr[Sr].Lson,Rr=Tr[Sr].Rson;
        if(Tr[Rr].C1-Tr[Lr].C1>=K) return Query3(Lr,Rr,Mid+1,Ri,K);
        return Query3(Ll,Rl,Le,Mid,K-Tr[Rr].C1+Tr[Lr].C1);
    }
    ll Query4(int Sl,int Sr,int Le,int Ri,int K)
    {   if(Le==Ri) return K<=Tr[Sr].C2-Tr[Sl].C2?Le:-Inf;
        int Mid=(1ll*Le+Ri)>>1,Ll=Tr[Sl].Lson,Lr=Tr[Sl].Rson,Rl=Tr[Sr].Lson,Rr=Tr[Sr].Rson;
        if(Tr[Rr].C2-Tr[Lr].C2>=K) return Query4(Lr,Rr,Mid+1,Ri,K);
        return Query4(Ll,Rl,Le,Mid,K-Tr[Rr].C2+Tr[Lr].C2);
    }
}using namespace TREE;
int main()
{   n=Read(),Qs=Read();
    for(int i=1;i<=n;i++) A[i]=Read();
    for(int i=1;i<=n+1;i++) B[i]=A[i]-A[i-1],Sum[i]=Sum[i-1]+Abs(B[i]);
    for(int i=1,L,R,K;i<=Qs;i++) L=Read(),R=Read(),K=Read(),Q[R].push_back((ELE){L,R,K,i});
    for(int i=1,L,R,K,Nr,Lt;i<=n+1;i++)
    {   Root[i]=Root[i-1],Modify(Root[i],0,Inf,1,2*Abs(B[i]),B[i]<0?1:2);
        Nr=Root[i],Lt=Ts;
        for(ELE Nq:Q[i])
        {   Sum1=0,L=Nq.L,R=Nq.R,K=Nq.K,Modify(Root[i],0,Inf,1,2*A[R],1);
            Modify(Root[i],0,Inf,-1,2*Abs(B[L]),B[L]<0?1:2),Modify(Root[i],0,Inf,1,2*A[L],2);
            for(int Le=1,Ri=n+1,Mid;Le<=Ri;)
            {   Mid=(Le+Ri)>>1,Ns=Query3(Root[L-1],Root[R],0,Inf,Mid)+Query4(Root[L-1],Root[R],0,Inf,Mid);
                if(Ns>2ll*K) Sum1=2ll*Mid*K-Query1(Root[L-1],Root[R],0,Inf,Mid)-Query2(Root[L-1],Root[R],0,Inf,Mid),Le=Mid+1;
                else Ri=Mid-1;
            }
            Ans[Nq.Id]=(Sum[R]-Sum[L]+A[L]+Abs(A[R])+Sum1)/2,Root[i]=Nr,Ts=Lt;
        }
    }
    for(int i=1;i<=Qs;i++) printf("%lld\n",Ans[i]);
}
posted @ 2021-08-08 23:52  Alkaid~  阅读(225)  评论(3编辑  收藏  举报