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

B.Black and white

题目链接

Black and white

简要题解

我们不难发现,只需要对\(n+m-1\)个格子进行染色,就可以将整个棋盘染成黑色。
我们把当前的黑格子聚在一起,形成一个\(i\)\(j\)列的矩阵,那么染一次色,就会使这个矩阵的行加一,或列加一。
初始我们染一次色,得到\(1\)\(1\)列的矩阵,扩展到\(n\)\(m\)列需要\(n+m-2\)次染色,因此总共是\(n+m-1\)次染色。
\(n+m-1\)这个数字其实可以认为是提示,因为\(n+m\)个点的树就是\(n+m-1\)条边。
我们把\(n\)\(m\)列看作\(n+m\)个点,把\(i\)\(j\)列的点染色,认为是在\(i\)行和\(j\)列对应点之间连了一条权值为\(c(i,j)\)的边。
那么我们发现,我们利用魔法变黑的那些点,并不会影响到这张图的连通性,花费代价染色的点,会合并两个连通块。
现在问题变得很清楚了,就是求着\(n+m\)个点的图的最小生成树。
由于点数少边数多,\(Prim\)算法比较合适。
不过此题边权较小,利用桶排实现的Kruskal算法也很优秀。
代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5010;
struct VEC{   int H,L;   };
int n,m,Cnt,Fa[MAXN*2];
ll Ans;
vector<VEC>Tong[100010];
void Input()
{   int A,B,C,D,P,Now;
    scanf("%d%d%d%d%d",&A,&B,&C,&D,&P),Now=A;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            Now=(1ll*Now*Now*B+1ll*Now*C+D)%P,Tong[Now].push_back((VEC){i,j});
}
int Find(int S){   return Fa[S]==S?S:Fa[S]=Find(Fa[S]);   }
int Merge(int A,int B){   return A=Find(A),B=Find(B),Fa[A]=B,A!=B;   }
int main()
{   scanf("%d%d",&n,&m),Input();
    for(int i=1;i<=n+m;i++) Fa[i]=i;
    for(int i=0;i<=100000&&Cnt<n+m-1;i++)
    {   for(VEC Pos:Tong[i])
            if(Merge(Pos.H,Pos.L+n)) Cnt++,Ans+=i;
    }
    printf("%lld\n",Ans);
}

C

简要题意

有一张\(n*n\)的网格图,里面只有\(m\)个位置可以放非负整数。
现在已知每一行的最大值{\(b_i\)}和每一列的最大值{\(c_i\)},求所有合法方案中,网格图内元素之和最小是多少。

数据范围

\(1\leq n\leq 2000,1\leq m\leq 8*10^5,1\leq b_i,c_i \leq 10^6\)

简要题解

我们已经知道每一行每一列的最大值,那么可以肯定的是,最优方案中的元素,要么是这些最大值之一,要么是0
不妨从大到小来考虑限制,因为我们很容易地能够得到最大元素的限制。
我们找出当前限制的最大值,再找出有哪些行列的的最大值是当前最大,单独考虑这些行列的交点。
那么我们必须选择若干个交点放置元素,来满足限制。一个交点可以满足一行和一列的限制,我们的目标是使用最少的交点来满足所有限制。
这就是个二分图匹配问题了,我们把代表行的点放在左边,代表列的点放在右边,再把每个交点对应的行和列连边,求最大匹配。
这个匹配的意义是:最多可以选择这么多点,使得这些点能够满足一行和一列的限制。而其他点只能满足一行或一列的限制。
那么需要的最少交点数就是:行数+列数-最大匹配数
我们把这些交点全部放上当前最大值,移除这个最大值,然后接着处理下一个最大值就行。
时间复杂度,大概是对\(4000\)个点求二分图匹配的时间复杂度,还能跑
代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=2010;
struct LIM{   int Lim,P;   }H[MAXN],L[MAXN];
bool Able[MAXN][MAXN];
int n,m,K,Hang[MAXN],Lie[MAXN];
ll Ans;
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;   }
bool cmp(LIM A,LIM B){   return A.Lim<B.Lim;   }
namespace Graph
{   vector<int>Edge[MAXN*2];
    int Cp[MAXN*2],Vis[MAXN*2];
    int Match(int Now,int Nc)
    {   int Flag=0;
        for(int v:Edge[Now])
        {   Flag=0,Vis[v]==Nc?Flag=1:Vis[v]=Nc;
            if(!Flag&&(!Cp[v]||Match(Cp[v],Nc))) return Cp[Now]=v,Cp[v]=Now,1;
        }
        return 0;
    }
    int Build(int Hs,int Ls)
    {   int Ret=0;
        for(int i=1;i<=Hs;i++) Edge[Hang[i]].clear();
        for(int i=1;i<=Ls;i++) Edge[Lie[i]+n].clear();
        for(int i=1;i<=Hs;i++)
            for(int j=1,A,B;j<=Ls;j++)
            {   if(Able[A=Hang[i]][B=Lie[j]]) Edge[A].push_back(B+n),Edge[B+n].push_back(A);
                Cp[A]=Cp[B+n]=Vis[A]=Vis[B+n]=0;
            }
        for(int i=1;i<=Hs;i++) if(!Cp[Hang[i]]) Ret+=Match(Hang[i],Hang[i]);
        return Hs+Ls-Ret;
    }
}using namespace Graph;
void Solve()
{   int Th=n,Tl=n,Maxs,Nh,Nl;
    while(Th>=1||Tl>=1)
    {   Maxs=Max(H[Th].Lim,L[Tl].Lim),Nh=0,Nl=0;
        while(Th>=1&&H[Th].Lim==Maxs) Hang[++Nh]=H[Th--].P;
        while(Tl>=1&&L[Tl].Lim==Maxs) Lie[++Nl]=L[Tl--].P;
        Ans+=1ll*Build(Nh,Nl)*Maxs;
    }
}
int main()
{   n=Read(),m=Read(),K=Read();
    for(int i=1;i<=n;i++) H[i].P=i,H[i].Lim=Read();
    for(int i=1;i<=n;i++) L[i].P=i,L[i].Lim=Read();
    for(int i=1,Nh,Nl;i<=m;i++) Nh=Read(),Nl=Read(),Able[Nh][Nl]=1;
    sort(H+1,H+n+1,cmp),sort(L+1,L+n+1,cmp),Solve(),printf("%lld\n",Ans);
}

G.Yu Ling(Ling YueZheng) and Colorful Tree

题目链接

Yu Ling(Ling YueZheng) and Colorful Tree

简要题解

官方题解是\(bitset\)和分块,不过大部分人都没有用这种写法。
我们的限制条件有三个,第一个是祖先,第二个是颜色区间,第三个是颜色倍数。
其实前两个限制比较常见,就是一个类似于二维数点的问题。祖先关系其实就是\(Dfn\)序上的一个区间,颜色也是一个区间,此处不展开叙述。
那么现在考虑如何处理第三个限制。
如果考虑所有操作,确实不太好想,但是如果我们每次只关心一部分操作,第三个限制就解决了。
具体地说,我们枚举一个\(x\),每次把对\(x\)颜色的询问和对\(x\)倍数的修改取出来,那么只有这里面的修改能满足第三个条件,且所有的修改都满足第三个条件,我们就在这些操作中解决询问即可。
这样的方法和虚树类似,我们每次只关心一些关键点,因此能够保证时间复杂度。
并且,题目保证每个点的颜色都不相同,这样的话,虽然一个修改操作会被取出多次,但是所有操作取出数量之和为\(nlogn\)
现在我们已经有了一个操作序列,并且没有了第三个限制条件,解决方法就多了。
常见的有树套树,这里写的是\(CDQ\)分治套主席树。
我们利用\(CDQ\)分治来解决时间顺序的影响,将操作序列按照执行时间排序,进行分治,每次只关心左边的修改对右边询问的贡献。
分治的每一层都建一棵主席树,注意一定只考虑关键点,不然时间复杂度不对。
左边的每一个操作,会影响\(Dfn\)上的一个区间,我们把这个作为主席树根的下标,在左端点加上贡献,右端点右边减去贡献。
对于每一个询问,我们要求满足条件的最近祖先,这个等价于求满足条件的祖先中的\(Deep\)最大值。
所以主席树维护颜色区间中的\(Deep\)最大值,在修改的颜色上加上修改点的\(Deep\)
然后处理右边的询问,就是在询问节点对应的主席树上,进行区间最值查询。
时间复杂度\(O(nlog^3n)\),操作数量一个\(log\)\(CDQ\)一个\(log\),主席树一个\(log\),不过不满,能跑过去。
代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1.1e5+10;
struct EDGE{   int u,v,w,Next;   }Edge[MAXN*2];
struct ELE{   int K,D,L,R,T,X;   }Opt[MAXN];
int n,Os,Es,First[MAXN];
ll Ans[MAXN];
vector<int>Q[MAXN],M[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 Max(ll A,ll B){   return A>B?A:B;   }
void Link(int u,int v,int w){   Edge[++Es]=(EDGE){u,v,w,First[u]},First[u]=Es;   }
namespace PRE
{   int Ds,Dfn[MAXN],Size[MAXN],Fa[MAXN];
    ll Deep[MAXN];
    void Dfs(int Now,int Ba)
    {   Dfn[Now]=++Ds,Size[Now]=1,Fa[Now]=Ba;
        for(int i=First[Now],v;i!=-1;i=Edge[i].Next)
            if((v=Edge[i].v)!=Ba) Deep[v]=Deep[Now]+Edge[i].w,Dfs(v,Now),Size[Now]+=Size[v];
    }
}using namespace PRE;
namespace TREE
{   int Ts,Root[MAXN];
    struct DOT{   int Lson,Rson;   ll Maxs;   }Tr[MAXN*20],Zero;
    void Push_up(int S){   Tr[S].Maxs=Max(Tr[Tr[S].Lson].Maxs,Tr[Tr[S].Rson].Maxs);   }
    void Modify(int &S,int Le,int Ri,int Aim,ll Num)
    {   Tr[++Ts]=Tr[S],S=Ts;
        if(Le==Ri) return Tr[S].Maxs=Num,(void)0;
        int Mid=(Le+Ri)>>1;
        Aim<=Mid?Modify(Tr[S].Lson,Le,Mid,Aim,Num):Modify(Tr[S].Rson,Mid+1,Ri,Aim,Num),Push_up(S);
    }
    ll Query(int S,int Le,int Ri,int Al,int Ar)
    {   if(Al<=Le&&Ri<=Ar) return Tr[S].Maxs;
        int Mid=(Le+Ri)>>1;
        return Max(Al<=Mid?Query(Tr[S].Lson,Le,Mid,Al,Ar):0,Mid<Ar?Query(Tr[S].Rson,Mid+1,Ri,Al,Ar):0);
    }
}using namespace TREE;
namespace CDQ
{   struct Pair{    int Np;   ll Dis;   };
    int Ss,Ld,Seq[MAXN*2],Dot[MAXN*4];
    vector<Pair>Do[MAXN];
    bool cmp(int A,int B){   return Opt[A].T<Opt[B].T;   }
    void CDQ(int Le,int Ri)
    {   if(Le==Ri) return ;
        int Mid=(Le+Ri)>>1;   ELE Nd;
        CDQ(Le,Mid),CDQ(Mid+1,Ri);
        for(int i=Le;i<=Ri;i++)
            if((Nd=Opt[Seq[i]]).K) Dot[++Ld]=Dfn[Nd.D];
            else Dot[++Ld]=Dfn[Nd.D],Dot[++Ld]=Dfn[Nd.D]+Size[Nd.D];
        sort(Dot+1,Dot+Ld+1),Ld=unique(Dot+1,Dot+Ld+1)-Dot-1;
        for(int i=Le;i<=Mid;i++)
            if(!(Nd=Opt[Seq[i]]).K)
                Do[Dfn[Nd.D]].push_back((Pair){Nd.X,Deep[Nd.D]}),Do[Dfn[Nd.D]+Size[Nd.D]].push_back((Pair){Nd.X,0});
        for(int i=1;i<=Ld;i++)
        {   Root[Dot[i]]=Root[Dot[i-1]];
            for(Pair j:Do[Dot[i]]) Modify(Root[Dot[i]],1,n,j.Np,j.Dis);
        }
        for(int i=Mid+1;i<=Ri;i++)
            if((Nd=Opt[Seq[i]]).K) Ans[Nd.T]=Max(Ans[Nd.T],Query(Root[Dfn[Nd.D]],1,n,Nd.L,Nd.R));
        for(int i=Le;i<=Mid;i++)
            if(!(Nd=Opt[Seq[i]]).K) Do[Dfn[Nd.D]].clear(),Do[Dfn[Nd.D]+Size[Nd.D]].clear();
        Ts=Ld=0;
    }
    void Solve()
    {   for(int i=1;i<=Os;i++)
            if(Opt[i].K) Q[Opt[i].X].push_back(i);
            else M[Opt[i].X].push_back(i);
        for(int i=1;i<=n;i++)
        {   if(!Q[i].size()) continue ;
            for(int j:Q[i]) Seq[++Ss]=j;
            for(int j=i;j<=n;j+=i)
                for(int k:M[j]) Seq[++Ss]=k;
            sort(Seq+1,Seq+Ss+1,cmp);
            CDQ(1,Ss),Ss=0;
        }
    }
}using CDQ::Solve;
int main()
{   n=Read(),Os=Read(),memset(First,-1,sizeof(First));
    for(int i=1,u,v,w;i<n;i++) u=Read(),v=Read(),w=Read(),Link(u,v,w),Link(v,u,w);
    for(int i=1;i<=Os;i++)
        Opt[i].K=Read(),Opt[i].D=Read(),Opt[i].K==1?(Opt[i].L=Read(),Opt[i].R=Read()):0,Opt[i].X=Read(),Opt[i].T=i;
    Deep[1]=1,Dfs(1,1),Solve();
    for(int i=1;i<=Os;i++)
        if(Opt[i].K) Ans[i]?printf("%lld\n",Deep[Opt[i].D]-Ans[i]):puts("Impossible!");
}

I

简要题意

给定一个长度为n的序列\({a_i}\),有m次修改操作,求经过m次操作后的序列。
修改操作分为两种:
1.给定\(l,r,x\),表示将\(l~r\)这一段的元素异或上x。
2.给定\(l,r,x\),表示将\(a_i\)异或上\(x+(i-l)\),其中\(l\leq i\leq r\)

数据范围

\(1\leq n\leq 6*10^5,1\leq q\leq 4*10^5,0\leq a_i\leq2^{30}\)

简要题解

对于第一种操作来说,我们可以直接利用差分来解决。
令$b_i=a_i\bigoplus a_{i-1} $ ,那么\(a_i=\bigoplus_{k=1}^ib_k\),对于每一个操作1来说,我们只需要将\(b_l\)\(b_{r+1}\)异或上\(x\)即可。
对于第二种操作来说,我们一位位地来考虑。
假设当前计算第\(B\)位的贡献,若第\(B\)位的值是1,则满足$2^B\leq x+i-l(mod 2^{B+1}) $,移项得到\(2^B+l-x\leq i < 2^{B+1}+l-x(mod 2^{B+1})\)
我们把n个数按顺序摆成\(2^{B+1}\)列的矩阵,即

\[a_1\quad a_2\quad a_3...a_{2^{B+1}} \]

\[a_{2^{B+1}+1}\quad a_{2^{B+1}+2}...a_{2^{B+2}} \]

\[... \]

\[...a_n \]

容易发现,只有一半连续的列(模意义下最左边和最右边连续),异或上当前数会改变第\(B\)位的值,并且可以知道是哪些列。
我们已知当前需要修改的区间为\([l,r]\),那么把它对应到矩形中的区域,再和上述连续的列求交,会发现我们需要修改的是若干个矩形区域。
可以利用差分的技巧来完成快速修改,最后统计答案时做一个二维前缀和就行。
时间复杂度\(O(n*loga)\)
代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=6e5+10;
struct IN{   int Le,Ri,X;   }Opt[MAXN];
int n,Qs,Os,A[MAXN],Hs[32],Ls[32];
ll Two[32];
vector<bool>M[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 Min(ll A,ll B){   return A<B?A:B;   }
ll Max(ll A,ll B){   return A>B?A:B;   }
void Modify(int H1,int L1,int H2,int L2)
{   M[H1][L1]=M[H1][L1]^1,M[H2+1][L1]=M[H2+1][L1]^1,M[H1][L2+1]=M[H1][L2+1]^1,M[H2+1][L2+1]=M[H2+1][L2+1]^1;   }
void Solve(int L,int R,int Minh,int K,int Nb)
{   int St=(Opt[K].Le-1)%Two[Nb+1]+1,End=(Opt[K].Ri-1)%Two[Nb+1]+1;
    if(L<=R)
    {   St=Max(St,L),End=Min(End,R);
        if(St<=End) Modify(Minh,St,Minh,End);
    }
    if(L>R)
    {   if(St<=R) Modify(Minh,St,Minh,Min(R,End));
        if(End>=L) Modify(Minh,Max(St,L),Minh,End);
    }
}
void Solve1(int L,int R,int Minh,int K,int Nb)
{   int St=(Opt[K].Le-1)%Two[Nb+1]+1;
    if(L<=R&&St<=R) St=Max(St,L),Modify(Minh,St,Minh,R);
    if(L>R)
    {   if(St<=R) Modify(Minh,St,Minh,R);
        St=Max(St,L),M[Minh][St]=M[Minh][St]^1,M[Minh+1][St]=M[Minh+1][St]^1;
    }
}
void Solve2(int L,int R,int Minh,int Maxh,int K,int Nb)
{   if(Minh>Maxh) return ;
    if(L<=R) Modify(Minh,L,Maxh,R);
    else Modify(Minh,1,Maxh,R),M[Minh][L]=M[Minh][L]^1,M[Maxh+1][L]=M[Maxh+1][L]^1;
}
void Solve3(int L,int R,int Maxh,int K,int Nb)
{   int End=(Opt[K].Ri-1)%Two[Nb+1]+1;
    if(L<=R&&End>=L) End=Min(End,R),Modify(Maxh,L,Maxh,End);
    if(L>R)
    {   if(End>=L) Modify(Maxh,L,Maxh,End);
        End=Min(End,R),Modify(Maxh,1,Maxh,End);
    }
}
int main()
{   n=Read(),Qs=Read(),Two[31]=1ll<<31;
    for(int i=0;i<=30;i++) Two[i]=1<<i,Ls[i]=Min(n,1ll<<(i+1)),Hs[i]=(n-1)/Ls[i]+1;
    for(int i=1;i<=n;i++) A[i]=Read();
    for(int i=n;i>=1;i--) A[i]^=A[i-1];
    for(int i=1,K,Le,Ri,X;i<=Qs;i++)
    {   K=Read(),Le=Read(),Ri=Read(),X=Read();
        if(K==0) A[Le]^=X,A[Ri+1]^=X;
        else Opt[++Os]=(IN){Le,Ri,X};
    }
    for(int Nb=0;Nb<=30;Nb++)
    {   for(int i=0;i<=Hs[Nb]+1;i++) M[i].clear();
        for(int i=0;i<=Hs[Nb]+1;i++)
            for(int j=0;j<=Ls[Nb]+1;j++) M[i].push_back(0);
        for(int i=1;i<=Os;i++)
        {   ll L=(Two[Nb]+Opt[i].Le-Opt[i].X%Two[Nb+1]+Two[Nb+1]-1)%Two[Nb+1]+1;
            ll R=(Two[Nb+1]+Opt[i].Le-Opt[i].X%Two[Nb+1]-2)%Two[Nb+1]+1;
            int Minh=(Opt[i].Le-1)/Two[Nb+1]+1,Maxh=(Opt[i].Ri-1)/Two[Nb+1]+1;
            if(L>n&&L<=R) continue ;
            if(L<=R&&R>n) R=n;
            if(L>R&&L>n) L=1;
            if(L>R&&R>n) R=n;
            if(Minh==Maxh) Solve(L,R,Minh,i,Nb);
            else Solve1(L,R,Minh,i,Nb),Solve2(L,R,Minh+1,Maxh-1,i,Nb),Solve3(L,R,Maxh,i,Nb);
        }
        for(int i=1,Cnt=0;i<=Hs[Nb];i++)
            for(int j=1;j<=Ls[Nb];j++)
                M[i][j]=M[i][j]^M[i][j-1]^M[i-1][j]^M[i-1][j-1],Cnt++,A[Cnt]^=M[i][j]<<Nb,A[Cnt+1]^=M[i][j]<<Nb;
    }
    for(int i=1;i<=n;i++) A[i]^=A[i-1],printf("%d ",A[i]);
}
posted @ 2021-07-25 19:29  Alkaid~  阅读(232)  评论(0编辑  收藏  举报