• 2021牛客暑期多校训练营7


    比赛链接:https://ac.nowcoder.com/acm/contest/11258

    F,H,I,12。讨论了半天K,还是不会做……

     

    B

    分析:

    区间操作题,要想办法在( logn )级别的时间内做完每个操作。

    如果把一个最长不减子序列分成两段,仍然可以算贡献,而且两段内部的贡献不受影响,只要考虑断点的改变;基于这种可拆分的性质,我们想到用线段树。

    线段树维护的东西是要用来计算两段“缝合”起来时改变的贡献的。可以看到这和前一段的最大值、最大值所在位置的( b )值有关。所以线段树要维护区间最大值和区间最大值所在位置的( b )值。

    接着,考虑查询。为了保证复杂度,需要在每个区间存下整个区间的答案;但是答案不仅和区间内部的值有关,还要考虑前一个接过来的元素的大小。换句话说,即使查询到了这个区间,取的也不一定是整个区间的答案,而是根据前面的数,从某个位置开始的子序列的答案。

    为了化解这一点,同时又快速查询,我们可以只记录这种情况:左区间取了最大值后右区间的答案。由于已经记录了区间最大值和对应的( b ),所以可以知道右区间的答案应该从哪个值开始。把右区间这个答案记成( cal[u] )。

    这样,每次线段树上( pushup )的时候就需要对右区间重新进行查询(因为起始值改变了);进行修改操作的复杂度就成了( O(log^2n) );

    而查询操作由于只有右区间能直接利用,所以左区间会一直查到单个位置,故查询复杂度(大约……)也是( O(log^2n) )。

    查询时务必注意前一段对后一段的影响!为此还要用结构体返回查询值。

    代码如下:

    #include<iostream>
    #define ls (u<<1)
    #define rs ((u<<1)|1)
    #define mid ((l+r)>>1)
    using namespace std;
    int const N=2e5+5;
    int n,a[N],b[N],q,mx[N<<2],pa[N<<2],cal[N<<2],lz[N<<2];
    struct Nd{
        int ans,x,p;
    };
    int rd()
    {
        int ret=0,f=1; char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
        while(ch>='0'&&ch<='9')ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar();
        return ret*f;
    }
    void pdn(int u)
    {
        if(!lz[u])return; lz[u]=0;
        pa[ls]^=1; lz[ls]^=1; pa[rs]^=1; lz[rs]^=1;
    }
    int findb(int u,int l,int r,int p)
    {
        if(l==r)return pa[u];
        pdn(u);
        if(p<=mid)return findb(ls,l,mid,p);
        else return findb(rs,mid+1,r,p);
    }
    Nd qry(int u,int l,int r,int ql,int qr,int x,int p)//ql~qr内,前一个数为x,其位置的b为p,取出最长不降子序列
    {
        if(ql>qr||x>mx[u])return (Nd){0,x,p}; Nd ans;
        //printf("qry(l=%d r=%d x=%d p=%d ql=%d qr=%d)
    ",l,r,x,p,ql,qr);
        if(l==r){ans=(mx[u]>=x)?(Nd){(pa[u]!=p),mx[u],pa[u]}:(Nd){0,x,p};///!
                    //printf("0ql=%d qr=%d l=%d r=%d x=%d p=%d = %d,%d,%d
    ",ql,qr,l,r,x,p,ans.ans,ans.x,ans.p);
                    return ans;
                }
        pdn(u);
        if(l>=ql&&r<=qr)
        {
            if(x<=mx[ls]){ans=qry(ls,l,mid,ql,qr,x,p); //ans.ans+=cal[u]; ans.x=mx[rs]; ans.p=pa[rs];
                            if(mx[rs]>=ans.x)ans.ans+=cal[u],ans.x=mx[rs],ans.p=pa[rs];///!
                    //printf("1ql=%d qr=%d l=%d r=%d x=%d p=%d = %d,%d,%d
    ",ql,qr,l,r,x,p,ans.ans,ans.x,ans.p);
                    return ans;}
            else {ans=qry(rs,mid+1,r,ql,qr,x,p);
                 //printf("2ql=%d qr=%d l=%d r=%d x=%d p=%d = %d,%d,%d
    ",ql,qr,l,r,x,p,ans.ans,ans.x,ans.p);
                 return ans;}
        }
        if(mid>=ql)
        {
            if(mid>=qr){ans=qry(ls,l,mid,ql,qr,x,p);
                //printf("3ql=%d qr=%d l=%d r=%d x=%d p=%d = %d,%d,%d
    ",ql,qr,l,r,x,p,ans.ans,ans.x,ans.p);
                return ans;}
            else
            {
                ans=qry(ls,l,mid,ql,qr,x,p); Nd a2=qry(rs,mid+1,r,ql,qr,ans.x,ans.p);
                //printf("4ql=%d qr=%d l=%d r=%d x=%d p=%d = %d,ansx=%d,ansp=%d,ansa=%d,a2x=%d,a2p=%d,a2a=%d
    ",ql,qr,l,r,x,p,ans.ans+a2.ans,ans.x,ans.p,ans.ans,a2.x,a2.p,a2.ans);
                return (Nd){ans.ans+a2.ans,a2.x,a2.p};
            }
        }
        else {ans=qry(rs,mid+1,r,ql,qr,x,p);
                 //printf("5ql=%d qr=%d l=%d r=%d x=%d p=%d = %d,%d,%d
    ",ql,qr,l,r,x,p,ans.ans,ans.x,ans.p);
                 return ans;}
    }
    void upt(int u,int l,int r)
    {
        if(mx[ls]>mx[rs])mx[u]=mx[ls],pa[u]=pa[ls],cal[u]=0;
        else mx[u]=mx[rs],pa[u]=pa[rs],cal[u]=qry(rs,mid+1,r,mid+1,r,mx[ls],pa[ls]).ans;
    }
    void build(int u,int l,int r)
    {
        if(l==r){mx[u]=a[l]; pa[u]=b[l]; return;}
        build(ls,l,mid); build(rs,mid+1,r);
        upt(u,l,r);
        //printf("l=%d r=%d mid=%d cal=%d
    ",l,r,mid,cal[u]);
    }
    void change1(int u,int l,int r,int ps,int x)//更新mx
    {
        if(l==r){mx[u]=x; return;}
        pdn(u);
        if(ps<=mid)change1(ls,l,mid,ps,x);
        else change1(rs,mid+1,r,ps,x);
        upt(u,l,r);
    }
    void change2(int u,int l,int r,int ql,int qr)//更新b
    {
        if(l>=ql&&r<=qr){pa[u]^=1; lz[u]^=1; return;}
        pdn(u);
        if(mid>=ql)change2(ls,l,mid,ql,qr);
        if(mid<qr)change2(rs,mid+1,r,ql,qr);
        upt(u,l,r);
    }
    int main()
    {
        n=rd();
        for(int i=1;i<=n;i++)a[i]=rd();
        for(int i=1;i<=n;i++)b[i]=rd();
        build(1,1,n); q=rd();
        for(int i=1,tp,t1,t2;i<=q;i++)
        {
            tp=rd(); t1=rd(); t2=rd();
            if(tp==1)a[t1]=t2,change1(1,1,n,t1,t2);
            if(tp==2)change2(1,1,n,t1,t2);
            if(tp==3)printf("%d
    ",qry(1,1,n,t1+1,t2,a[t1],findb(1,1,n,t1)).ans);
        }
        return 0;
    }
    me

    F

    分析:

    第一棵树(下称树1)中选取的链一定是连续的,容易想到dfs;因为dfs到的当前点的祖先链都已经dfs过了。

    所以我们想利用]( fa[u] )的答案来计算( u )的答案。我们可以考虑每个点向上最长能延伸多少个点。

    在树2上的祖先关系可以转化成dfs序上的区间覆盖问题。那么,在对树1dfs的过程中,我们希望记录下当前点祖先在树2上的覆盖信息,以此计算答案。因为dfs有撤销的过程,所以我们可以用主席树来实现:

    对于树1上dfs到的点(u),继承(fa[u])的主席树,然后在(u)的dfs序子树区间上覆盖(dep[u])这个值;

    查询(fa[u])的主席树上(u)子树区间内的最大值,这个值表示会与(u)产生冲突的最深的祖先点;所以(ans[u]=dep[u]-query(fa[u], l[u], r[u]) );

    但是还要考虑到(u)的祖先点彼此之间的冲突;由于(u)向上延伸的链和(fa[u])向上延伸的链是重合的,所以(ans[u])只需要再对(ans[fa[u]])取个(min)值即可。

    注意主席树上(lazy)标记的用法……挺妙的。

    时间复杂度(O(nlogn))。

    代码如下:

    #include<iostream>
    #include<cstring>
    #define mid ((l+r)>>1)
    using namespace std;
    int const N=3e5+5;
    int T,n,hd1[N],cnt1,nxt1[N<<1],to1[N<<1],hd2[N],cnt2,nxt2[N<<1],to2[N<<1];
    int tim,dfn[N],siz[N],dep[N],ans[N];
    int rt[N],tcnt,ls[N<<5],rs[N<<5],mx[N<<5],lz[N<<5];
    int rd()
    {
        int ret=0,f=1; char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
        while(ch>='0'&&ch<='9')ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar();
        return ret*f;
    }
    void add1(int x,int y){nxt1[++cnt1]=hd1[x]; hd1[x]=cnt1; to1[cnt1]=y;}
    void add2(int x,int y){nxt2[++cnt2]=hd2[x]; hd2[x]=cnt2; to2[cnt2]=y;}
    void init()
    {
        cnt1=0; cnt2=0; memset(hd1,0,sizeof hd1); memset(hd2,0,sizeof hd2);
        tim=0; memset(ans,0,sizeof ans);
        tcnt=0; memset(rt,0,sizeof rt); memset(mx,0,sizeof mx);
    }
    void dfs2(int u,int fa)
    {
        dfn[u]=++tim; siz[u]=1;
        for(int i=hd2[u],v;i;i=nxt2[i])
            if((v=to2[i])!=fa)dfs2(v,u),siz[u]+=siz[v];
    }
    int build(int l,int r)
    {
        int ret=++tcnt; mx[ret]=0; lz[ret]=0;
        if(l>=r)return ret;
        ls[ret]=build(l,mid);
        rs[ret]=build(mid+1,r);
        return ret;
    }
    int qry(int u,int l,int r,int ql,int qr)
    {
        if(l>=ql&&r<=qr)return mx[u];
        int ret=lz[u];//
        if(mid>=ql)ret=max(ret,qry(ls[u],l,mid,ql,qr));
        if(mid<qr)ret=max(ret,qry(rs[u],mid+1,r,ql,qr));
        return ret;
    }
    int update(int u,int l,int r,int ql,int qr,int x)
    {
        int ret=++tcnt;
        mx[ret]=max(mx[u],x); ls[ret]=ls[u]; rs[ret]=rs[u]; lz[ret]=0;//
        if(l>=ql&&r<=qr){lz[ret]=x; return ret;}//
        if(mid>=ql)ls[ret]=update(ls[u],l,mid,ql,qr,x);
        if(mid<qr)rs[ret]=update(rs[u],mid+1,r,ql,qr,x);
        return ret;
    }
    void dfs1(int u,int fa)
    {
        dep[u]=dep[fa]+1;
        if(fa)ans[u]=min(ans[fa]+1,dep[u]-qry(rt[fa],1,n,dfn[u],dfn[u]+siz[u]-1));
        else ans[u]=1;
        //printf("ans[%d]=%d
    ",u,ans[u]);
        rt[u]=update(rt[fa],1,n,dfn[u],dfn[u]+siz[u]-1,dep[u]);
        for(int i=hd1[u],v;i;i=nxt1[i])
            if((v=to1[i])!=fa)dfs1(v,u);
    }
    int main()
    {
        T=rd();
        while(T--)
        {
            n=rd(); init();
            for(int i=1,u,v;i<n;i++)
                u=rd(),v=rd(),add1(u,v),add1(v,u);
            for(int i=1,u,v;i<n;i++)
                u=rd(),v=rd(),add2(u,v),add2(v,u);
            dfs2(1,0);
            rt[0]=build(1,n);
            dfs1(1,0);
            int anss=0;
            for(int i=1;i<=n;i++)anss=max(anss,ans[i]);
            printf("%d
    ",anss);
        }
        return 0;
    }
    me

    H

    分析:

    签到题。用桶记录出现的数,直接枚举因子计数。复杂度( O(nlogn) )。

    I

    分析:

    签到题。按位考虑。

    J

    分析:

    题解第一种做法,有点不好想到,但复杂度靠谱。

    还看到一种比较直接的做法,就是对错误Floyd各种剪枝。核心思想是对于转移方程( dis[i][j] = min(dis[i][j], dis[i][k]+dis[k][j]) ),只遍历有可能使( dis[i][j] )正确的那些( k )。

    错误Floyd的转移有很整齐的顺序,对于(k)我们可以分成小于(j)的和大于(j)的。小于(j)的(k)在前面已经转移得到了( dis[i][k] ),而大于(j)的(k)除了直接与(i)连边的,其他都是(dis[i][k]=inf)。

    所以对于大于(j)的(k),( dis[i][k] = inf )的(k)就不遍历了,也就是只找和(i)直接连边的点(k);

    对于小于(j)的(k),只考虑有可能使接下来的(dis[i][j])答案正确的。也就是如果(dis[i][j])答案正确,就把(j)加到(i)要遍历的点集里。

    这个点集初始只有(i)直接相连的点。每次枚举到一个(j),遍历这个点集中的点来更新答案。

    但是这样还是会TLE。再加点剪枝,比如正确答案就是(inf)的就不更新了,还有已经是正确答案的也不更新了。

    时间复杂度??,呃反正是能过,还不慢呢。(毕竟各种做法都是加速错误Floyd这个过程)

    代码如下:

    #include<iostream>
    #include<queue>
    #include<cstring>
    #include<vector>
    #define pb push_back
    using namespace std;
    int const N=2005,M=5005;
    int n,m,d[N][N],dis[N][N],hd[N],cnt,nxt[M],to[M],w[M],inf;
    bool vis[N];
    struct Nd{
        int d,id;
        bool operator < (const Nd &a) const
        {return d>a.d;}
    };
    priority_queue<Nd>q;
    vector<int>ed[N];
    void add(int x,int y,int t){nxt[++cnt]=hd[x]; hd[x]=cnt; to[cnt]=y; w[cnt]=t;}
    void dijk(int st)
    {
        memset(vis,0,sizeof vis); q.push((Nd){0,st});
        while(q.size())
        {
            int nd=q.top().d,u=q.top().id; q.pop();
            if(vis[u])continue; vis[u]=1;
            for(int i=hd[u],v;i;i=nxt[i])
            {
                if(vis[v=to[i]])continue;
                if(d[st][v]>nd+w[i])
                    d[st][v]=nd+w[i],q.push((Nd){d[st][v],v});
            }
        }
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        memset(d,0x3f3f3f3f,sizeof d); memset(dis,0x3f3f3f3f,sizeof dis); inf=d[0][0];
        for(int i=1;i<=n;i++)d[i][i]=0,dis[i][i]=0;
        for(int i=1,u,v,t;i<=m;i++)
        {
            scanf("%d%d%d",&u,&v,&t);
            add(u,v,t); dis[u][v]=t; ed[u].pb(v);
        }
        for(int i=1;i<=n;i++)dijk(i);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
            {
                if(d[i][j]==dis[i][j]||d[i][j]==inf)continue;
                for(int k:ed[i])
                    dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
                if(d[i][j]==dis[i][j])ed[i].pb(j);
            }
        int ans=0;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                ans+=(d[i][j]==dis[i][j]);//,printf("d[%d][%d]=%d dis[%d][%d]=%d
    ",i,j,d[i][j],i,j,dis[i][j]);
        printf("%d
    ",ans);
        return 0;
    }
    me

    K

    分析:

    比赛时想了半天,也想过贪心之类的,较小的数减成0、较大的数加成( k )这种。但是区间内的元素彼此牵连,总是很乱不能做。

    看了题解才发现应该差分,这样区间加减就变成单点加减了;区间归零也可以转换成差分归零。一下子清晰好多。

    先考虑只有一次询问而且没有( k )的情况要怎么做:在差分数组( b_i )上,一次操作会把某个位置( +1 ),另一个位置( -1 );而差分数组的总和等于( 0 )(在原数组前后插两个( 0 ),差分数组一共( n+1 )个元素)。所以最小次数就是( sum_{i=1}^{i=n+1} |b_i| / 2 )。

    现在考虑引入( k ):每个差分既可以变成( 0 ),也可以变成( pm k )。为了方便考虑,这时我们可以把负数( x )变为正数( k+x ),这样所有数都在( [0,k) )这个区间里。可以想到贪心的做法就是让较小的数变成( 0 ),较大的数变成( k );我们可以把整个数组排序,然后二分一下在哪里分界。当把较小数变成( 0 )的操作次数( pref )与把较大数变成( k )的操作次数( suf )最接近时,答案最优,二分结束。

    现在,回到我们的问题:有多次询问,每次询问有一个区间和一个( k )。这要求我们在( logn )级别的时间内把一个区间的差分取出来,负数变成正数(这个过程和( k )有关,不能预处理),然后排序,再二分。为了快速取区间并排序,可以想到对差分序列建立权值主席树(主席树额外的好处是动态建点,不需要离散化)。

    但是,负数变正数与( k )有关,还是不能做。我们可以干脆分开正负考虑;对于负数( x ),我们原本把它变成( k+x ),然后与二分到的值( mid )作比较;现在我们保留( x ),让它与( mid-k )作比较。这样就在二分的过程中解决负数的问题了。

    还要注意值恰好是二分的( mid )的那些数;它们可以一部分取到( 0 ),一部分取到( pm k ),所以这里还需要再二分,求最优的答案。

    时间复杂度( O(nlogk+qlog^2k) )。

    写完不知为何TLE了,感觉不是时间复杂度的问题,应该是某些递归边界没写好。后来把主席树查询函数改了一下,当前区间没有值了就直接返回,就过了。仔细想想,不判断这个的话会把范围内的所有数不停地找下去,确实会TLE,和我一开始要在( 0 )号点build一个主席树犯了一样的错误。笑。

    代码如下:

    #include<iostream>
    #define ll long long
    #define mid ((l+r)>>1)
    using namespace std;
    int const N=2e5+5;
    ll const inf=1e17;
    int n,q,a[N],b[N],up,dn;
    int cnt,rt[2][N],ls[2][N<<5],rs[2][N<<5],num[2][N<<5];
    ll sum[2][N<<5];
    struct Nd{
        ll s; int num;
        Nd operator + (const Nd &a) const
            {return (Nd){s+a.s,num+a.num};}
    };
    int rd()
    {
        int ret=0,f=1; char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
        while(ch>='0'&&ch<='9')ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar();
        return ret*f;
    }
    /*
    int build(int tp,int l,int r)
    {
       int ret=++cnt;
       if(l>=r)return ret;
       ls[tp][ret]=build(tp,l,mid); rs[tp][ret]=build(tp,mid+1,r);
       return ret;
    }
    */
    int update(int tp,int pre,int l,int r,int x)
    {
        int ret=++cnt;
        ls[tp][ret]=ls[tp][pre]; rs[tp][ret]=rs[tp][pre];
        sum[tp][ret]=sum[tp][pre]+x; num[tp][ret]=num[tp][pre]+1;
        if(l==r)return ret;
        if(x<=mid)ls[tp][ret]=update(tp,ls[tp][pre],l,mid,x);
        else rs[tp][ret]=update(tp,rs[tp][pre],mid+1,r,x);
        return ret;
    }
    void Build()
    {
        //rt[0][0]=build(0,0,up); rt[1][0]=build(1,1,dn);
        for(int i=1;i<=n;i++)//
        {
            if(b[i]>=0)
            {
                rt[0][i]=update(0,rt[0][i-1],0,up,b[i]);
                rt[1][i]=rt[1][i-1];
            }
            else
            {
                rt[0][i]=rt[0][i-1];
                rt[1][i]=update(1,rt[1][i-1],1,dn,-b[i]);
            }
        }
    }
    Nd qry(int tp,int pre,int nw,int l,int r,int ql,int qr)
    {
        if(ql>qr||l>r||num[tp][nw]-num[tp][pre]==0)return (Nd){0,0};//!
        //if(ql>qr||l>r||l>qr||r<ql)return (Nd){0,0};//TLE
        if(l>=ql&&r<=qr)return (Nd){sum[tp][nw]-sum[tp][pre],num[tp][nw]-num[tp][pre]};
        Nd ret=(Nd){0,0};
        if(mid>=ql)ret=ret+qry(tp,ls[tp][pre],ls[tp][nw],l,mid,ql,qr);
        if(mid<qr)ret=ret+qry(tp,rs[tp][pre],rs[tp][nw],mid+1,r,ql,qr);
        return ret;
    }
    int main()
    {
        n=rd(); q=rd();
        for(int i=1;i<=n;i++)
        {
            a[i]=rd(); b[i]=a[i]-a[i-1];
            if(b[i]>up)up=b[i]; if(b[i]<dn)dn=b[i];
        }
        //b[n+1]=0-a[n];
        //if(b[n+1]>up)up=b[n+1]; if(b[n+1]<dn)dn=b[n+1];
        dn=-dn; Build();
        for(int i=1,L,R,k;i<=q;i++)
        {
            L=rd(); R=rd(); k=rd();
            Nd qup1,qup2,qdn1,qdn2; int l=0,r=k; ll ans=inf;
            while(l<=r)
            {
                //printf("l=%d r=%d mid=%d
    ",l,r,mid);
                qup1=qry(0,rt[0][L],rt[0][R],0,up,0,mid-1); qup2=qry(0,rt[0][L],rt[0][R],0,up,mid+1,up);
                qdn1=qry(1,rt[1][L],rt[1][R],1,dn,1,k-mid-1); qdn2=qry(1,rt[1][L],rt[1][R],1,dn,k-mid+1,dn);
    
    
                int midc=(R-L)-qup1.num-qup2.num-qdn1.num-qdn2.num;
                if(a[L]<mid)qup1.s+=a[L],qup1.num++;
                else if(a[L]>mid)qup2.s+=a[L],qup2.num++;
                else midc++;
                /*
                printf("midc=%d
    ",midc);
                printf("up1=%lld cnt1=%d up2=%lld cnt2=%d
    ",qup1.s,qup1.num,qup2.s,qup2.num);
                printf("dn1=%lld cnt1=%d dn2=%lld cnt2=%d
    ",qdn1.s,qdn1.num,qdn2.s,qdn2.num);*/
    
                ll pref=qup1.s+((ll)k*qdn2.num-qdn2.s);
                ll suf=((ll)k*qup2.num-qup2.s)+qdn1.s;
                if(!midc)ans=min(ans,max(pref,suf));
                else
                {
                    int cl=0,cr=midc;
                    while(cl<=cr)
                    {
                        int Mid=((cl+cr)>>1);
                        ll cpref=pref+(ll)Mid*mid, csuf=suf+(ll)(midc-Mid)*(k-mid);
                        ans=min(ans,max(cpref,csuf));
                        if(cpref<csuf)cl=Mid+1;
                        else cr=Mid-1;
                    }
                }
    
                if(pref<suf)l=mid+1;
                else r=mid-1;
            }
            printf("%lld
    ",ans);
        }
        return 0;
    }
    me
  • 相关阅读:
    泛型的内部原理:类型擦除以及类型擦除带来的问题
    Redis的那些最常见面试问题
    线程池全面解析
    对线程调度中Thread.sleep(0)的深入理解
    集群环境下Redis分布式锁
    3.8
    3.7
    3.6任务
    3.5任务
    3.4
  • 原文地址:https://www.cnblogs.com/Zinn/p/15114184.html
Copyright © 2020-2023  润新知