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

E

简要题意

给定一棵n个点的树,每个点有点权\(a_i\),每条边有边权\(w_i\)
现有\(q\)个询问,每个询问给定\(x_i\),\(d_i\),\(p_i\),表示初始权值为\(d_i\),从\(x_i\)出发,不能经过\(p_i\),能够到达的点的数量。
每经过一个点,权值加上该点的点权,每经过一条边,权值减去该边的边权,要求任意时刻的权值非负。

数据范围

\(n\leq 10^5,q\leq 10^5 , 1\leq a_i,w_i\leq 10^9 ,1\leq x_i \leq n ,0\leq d_i\leq 10^{14},p_i\neq x_i\)

简要题解

此题涉及到的是点对之间的关系,故点分治是一种优秀的解题方法。
首先将询问离线,挂链到\(x_i\)上,然后点分治,点分治的基本操作不再赘述。
假设当前区域重心为\(rt\),我们需要考虑的是区域内经过重心的点对贡献,所以需要预处理两个数组,记为\(Down[i]\)\(Up[i]\)
\(Down[i]\)表示,从\(rt\)出发,到达点\(i\),需要的最小初始权值;\(Up[i]\)表示,从\(i\)出发,到达\(rt\),需要的最小初始权值。
于是我们就能够很容易地知道,哪些询问可以走到\(rt\)且还剩下多少权值。
\(Down[i]\)离散化后插入树状数组,我们就能够快速知道,当某个询问走到\(rt\)后,剩下的权值能够走到多少个点。
这里会有一些不合法的情况,因为我们强制询问先走到\(rt\)再向下走,和询问在同一子树内的点就不能计算答案,还有经过了\(p_i\)的不能算入答案。
如果\(p_i\)不在当前点分治区域内,就直接忽略。如果在,经过\(p_i\)的情况有三种,一是\(p_i\)\(x_i\)\(rt\)的路径上,那么本层不可能不会对该询问产生贡献。二是\(p_i\)\(x_i\)在同一子树内,这种情况不需要单独考虑,因为同一子树本来就不能计算贡献。三是\(p_i\)\(x_i\)\(rt\)的不同子树内,需要减去\(p_i\)子树的贡献。
至于如何得到子树贡献,可以利用树状数组作差,Dfs到达某点后,先计算树状数组内满足条件的点数,再把当前点贡献加入树状数组,Dfs子树,再计算树状数组内满足条件的点数,两个点数作差就是子树内的贡献。
代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=1e5+10;
struct EDGE{   int u,v,w,Next;   }Edge[MAXN<<1];
struct QUE{   ll Gas;   int Ban;   }Q[MAXN];
struct OPT{   ll Lim;   int Id,K;   };
int n,Qs,Es,First[MAXN];
ll Ans[MAXN],Gas[MAXN];
vector<int>List[MAXN];
vector<OPT>Opt[MAXN];
ll Read()
{   ll 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 Link(int u,int v,int w){   Edge[++Es]=(EDGE){u,v,w,First[u]},First[u]=Es;   }
namespace BIT
{   int Top,Tr[MAXN];
    void Clear(int S){   for(Top=S;S;S--) Tr[S]=0;   }
    int Lowbit(int K){   return K&(-K);   }
    int Query(int S)
    {   int Ret=0;
        while(S>=1) Ret+=Tr[S],S-=Lowbit(S);
        return Ret;
    }
    void Add(int S,int K){   while(S<=Top) Tr[S]+=K,S+=Lowbit(S);   }
}using BIT::Add;using BIT::Query;using BIT::Clear;
namespace Dot
{   int Rt,Heavy,Ds,Ls;
    int Fa[MAXN],Anc[MAXN],Size[MAXN],Dfn[MAXN],Idfn[MAXN],Vis[MAXN],Col[MAXN];
    ll Lsh[MAXN],Dis[MAXN],Up[MAXN],Down[MAXN];
    void Get_Root(int Now,int Ba,int All)
    {   int Maxv=0;   Size[Now]=1;
        for(int i=First[Now],v;i!=-1;i=Edge[i].Next)
        {   if(Vis[v=Edge[i].v]||v==Ba) continue ;
            Get_Root(v,Now,All),Size[Now]+=Size[v],Maxv=Max(Maxv,Size[v]);
        }
        Maxv=Max(Maxv,All-Size[Now]),Maxv<Heavy?(Heavy=Maxv,Rt=Now):0;
    }
    void Dfs1(int Now,int Ba)
    {   Idfn[Dfn[Now]=++Ds]=Now,Size[Now]=1,Fa[Now]=Ba;
        Anc[Now]=(Ba==Rt?Now:Anc[Ba]),Col[Now]=Rt,Lsh[++Ls]=-Down[Now];
        for(int i=First[Now],v,w;i!=-1;i=Edge[i].Next)
        {   v=Edge[i].v,w=Edge[i].w;
            if(Vis[v]||v==Ba) continue ;
            Dis[v]=Dis[Now]+Gas[Now]-w,Down[v]=Min(Dis[v],Down[Now]);
            Up[v]=Min(0,Min(Up[Now]-w+Gas[v],Dis[Now]-Gas[Rt]*(Rt!=Now)+Gas[v]+Gas[Now])),Dfs1(v,Now),Size[Now]+=Size[v];
        }
    }
    void Dfs2(int Now)
    {   for(OPT Np:Opt[Now])
            Ans[Np.Id]-=Query(Np.Lim)*Np.K;
        Add(Down[Now],1);
        for(int i=First[Now],v;i!=-1;i=Edge[i].Next)
            if(!Vis[v=Edge[i].v]&&v!=Fa[Now]) Dfs2(v);
        for(OPT Np:Opt[Now])
            Ans[Np.Id]+=Query(Np.Lim)*Np.K;
    }
    void Divide(int St,int Hr)
    {   Ds=Ls=0,Heavy=Hr,Get_Root(St,St,Heavy),Vis[Rt]=1;
        Down[Rt]=Up[Rt]=0,Dis[Rt]=0,Dfs1(Rt,Rt);
        sort(Lsh+1,Lsh+Ls+1),Ls=unique(Lsh+1,Lsh+Ls+1)-Lsh-1;
        for(int i=1,Now;i<=Ds;i++)
            Now=Idfn[i],Up[Now]*=-1,Down[Now]*=-1,Down[Now]=lower_bound(Lsh+1,Lsh+Ls+1,Down[Now])-Lsh,Opt[Now].clear();
        for(int i=1,Now;i<=Ds;i++)
            for(int Nq:List[Now=Idfn[i]])
            {   int Ban=Q[Nq].Ban;   ll Lim=Dis[Now]+Q[Nq].Gas+Gas[Now]-Gas[Rt];
                if(Up[Now]>Q[Nq].Gas) continue ;
                if(Dfn[Ban]<=Dfn[Now]&&Dfn[Now]<Dfn[Ban]+Size[Ban]&&Col[Ban]==Rt) continue ;
                Lim=upper_bound(Lsh+1,Lsh+Ls+1,Lim)-Lsh-1,Opt[Rt].push_back((OPT){Lim,Nq,1});
                if(Now!=Rt) Opt[Anc[Now]].push_back((OPT){Lim,Nq,-1});
                else Ans[Nq]--;
                if(Anc[Ban]!=Anc[Now]&&Col[Ban]==Rt) Opt[Ban].push_back((OPT){Lim,Nq,-1});
            }
        Clear(Ls),Dfs2(Rt);
        for(int i=First[Rt],v;i!=-1;i=Edge[i].Next)
            if(!Vis[v=Edge[i].v]) Divide(v,Size[v]);
    }
}using Dot::Divide;
int main()
{   n=Read(),Qs=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<=n;i++) Gas[i]=Read();
    for(int i=1,X;i<=Qs;i++)
        X=Read(),Q[i].Gas=Read(),Q[i].Ban=Read(),List[X].push_back(i);
    Divide(1,n);
    for(int i=1;i<=Qs;i++) printf("%lld\n",Ans[i]+1);
}

G

简要题意

给定\(n\)个区间\((l_i,r_i)\),将这\(n\)个区间分成\(k\)组。要求每组中的区间必须有交,对于每一种划分方案,求每组区间交的长度的和,求所有方案中这个和最大是多少。

数据范围

\(1\leq n,k\leq5000\),\(0\leq l_i,r_i\leq 10^5\)

简要题解

对于处理若干区间的题,通常需要进行预处理,去掉完全包含或被包含的区间。
如果存在区间\(A\),能够完全包含区间\(B\),那么\(A\)的分组情况有两种:要么自己单独成为一组,贡献为该区间的长度,要么和它的任一子区间成为一组,贡献为0。
否则,假设区间A和区间C一组,且A与C没有包含关系,那么将A与B分到一组会更优。
接下来我们将区间分成两类\({S_i}\)\({T_i}\)\({S_i}\)是能够完全包含某一区间的区间,那么\({T_i}\)中的区间就没有包含关系了。
我们对\({S_i}\)按长度降序排序,\({T_i}\)按右端点坐标升序排序,可以证明,最优的划分方案,一定是将\({T_i}\)分成若干组,且组内下标连续,再把\(S_i\)中最长的若干个区间单独成组。
根据这个思路不难想到动态规划,设\(F[i][j]\)表示,将\({T}\)中前\(i\)个区间分成\(j\)组,能够达到的最大贡献,若不合法则设为\(-inf\)
状态转移方程:$$F[i][j]=max(F[k-1][j-1]+r_k-l_i+1)$$
需要保证\(k\leq i\)\(r_k\geq l_i\)
这里表示将\(T_k\)~\(T_i\)分成一组的情况,枚举\(i,j,k\),时间复杂度为\(O(n^3)\)
改写一下转移方程,进行分离变量:$$F[i][j]+l_i-1=max(F[k-1][j-1]+r_k)$$
方程右边是一个只和\(k\)有关的值,可以进行单调队列优化\(Dp\)
即对于每一个\(j\),维护一个单调队列\(Q[j]\),队列中每个元素储存\(F[k-1][j-1]\)\(r_k\),保证\(r_k\)单增,\(F[k-1][j-1]+r_k\)单减。
每次转移前弹队首,将\(Q_{j-1}\)\(r_k<l_i\)的扔掉,再直接利用第一个元素转移,并将转移后的信息放入\(Q_j\)队尾。
这样我们得到了\(F[i][j]\)的值,然后枚举一下\({S_i}\)中有几个单独成组,和\(F[i][j]\)合并计算答案即可。
时间复杂度可以优化至\(O(n^2)\)
代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=5010;
const ll Inf=1e18;
struct SEC{   int Le,Ri;   }Sec[MAXN];
struct ELE{   int Ri;   ll Num;   };
bool Big[MAXN];
int n,K,Bs;
ll Ans,Blen[MAXN],F[MAXN][MAXN];
deque<ELE>Team[MAXN];
bool cmp2(ll A,ll B){   return A>B;   }
bool cmp(SEC A,SEC B){   return A.Ri==B.Ri?A.Le>B.Le:A.Ri<B.Ri;   }
ll Max(ll A,ll B){   return A>B?A:B;   }
void Prepare()
{   int Maxl=-1;
    sort(Sec+1,Sec+n+1,cmp);
    for(int i=1;i<=n;i++)
    {   Big[i]=Sec[i].Le<=Maxl,Maxl=Max(Maxl,Sec[i].Le);
        if(Big[i]) Blen[++Bs]=Sec[i].Ri-Sec[i].Le+1;
    }
    for(int i=1;i<=n;i++)
        while(Big[i]&&i<=n) swap(Sec[i],Sec[n]),swap(Big[i],Big[n]),n--;
    sort(Sec+1,Sec+n+1,cmp),sort(Blen+1,Blen+Bs+1,cmp2);
    for(int i=1;i<=Bs;i++) Blen[i]+=Blen[i-1];
}
void Solve()
{   for(int i=0;i<=n;i++)
        for(int j=0;j<=K;j++) F[i][j]=-Inf;
    F[0][0]=0;
    for(int i=0;i<=K;i++) Team[i].push_back((ELE){Sec[1].Ri,F[0][i]+Sec[1].Ri});
    for(int i=1;i<=n;i++)
        for(int j=K;j>=1;j--)
        {   while(!Team[j-1].empty()&&Team[j-1][0].Ri<Sec[i].Le) Team[j-1].pop_front();
            if(Team[j-1].empty()) continue ;
            F[i][j]=Team[j-1].front().Num-Sec[i].Le+1;
            ll Num=F[i][j]+Sec[i+1].Ri;
            while(!Team[j].empty()&&Team[j].back().Num<=Num) Team[j].pop_back();
            Team[j].push_back((ELE){Sec[i+1].Ri,Num});
        }
    for(int i=0;i<=K;i++) Ans=Max(Ans,F[n][i]+Blen[K-i]);
}
int main()
{   scanf("%d%d",&n,&K);
    for(int i=1;i<=n;i++) scanf("%d%d",&Sec[i].Le,&Sec[i].Ri),Sec[i].Ri--;
    Prepare(),Solve(),printf("%lld\n",Ans);
}

J

简要题意

给定集合\(S\),集合内的元素为整数\(x_i\)\(T\)为集合\(S\)的子集,\(|T|=k\),记\(f(T)\)\(T\)内所有元素的\(gcd\),求$$\prod_Tf(T)$$
答案对P取模

数据范围

多组数据,数据组数\(t\leq60\)\(10^6\leq P\leq10^{14}\)\(1\leq x_i\leq 8*10^4\)\(|S|\leq 4*10^4\)\(1\leq k\leq min(|S|,30)\)

简要题解

答案是所有gcd的乘积,所以可以枚举质因子及其次幂,单独计算贡献。
假设枚举到质因子\(q\)以及次幂\(x\),不难求出恰好拥有\(x\)\(q\)的数的个数\(A\),以及拥有超过\(x\)\(q\)的数的个数\(B\),那么这里产生的贡献是 $$ q^{x*(C(A+B,k)-C(B,k))} $$
指数太大,需要对\(\phi(P)\)取模,即P的欧拉函数
综上,此题有几个关键步骤:
1.欧拉筛法求出\(10^7\)之内的质数
2.利用质数表求出\(\phi(P)\)
3.递推求出组合数
4.枚举\(q\)\(x\)计算贡献
5.注意卡常

P太大,可以用__int128来计算,只需要计算过程转换,为了卡常,储存还是用longlong
求组合数的时候用减法代替取模
其他地方能优化就优化
代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN=4e4+10;
const int MAXM=1e7+10;
bool Notp[MAXM];
int T,Ls,Ps,K,Pps;
int Seq[MAXN],Pri[MAXN*20],Minp[MAXM],Top[MAXN];
int Own[MAXN*2][22];
ll P,Ep;
ll C[MAXN][35];
ll Ans=1;
inline 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;
}
inline ll Add(ll A,ll B){   return A+=B,A>=Ep?A-Ep:A;   }
inline ll Mul(ll A,ll B){   return (__int128)A*B%P;   }
void Euler()
{   ll Np=P;
    for(int i=1;1ll*Pri[i]*Pri[i]<=P&&i<=Pps;i++)
    {   if(Np%Pri[i]) continue ;
        Ep*=(Pri[i]-1),Np/=Pri[i];
        while(Np%Pri[i]==0) Ep*=Pri[i],Np/=Pri[i];
    }
    if(Np>1) Ep*=Np-1;
}
void Combine()
{   for(int i=0;i<=40000;i++)
    {   C[i][0]=1;
        for(int j=1;j<=K&&j<=i;j++) C[i][j]=Add(C[i-1][j],C[i-1][j-1]);
    }
}
inline ll Pow(ll Down,ll Up)
{   ll Ret=1,Now=Down;
    while(Up) Up&1?Ret=Mul(Ret,Now):0,Now=Mul(Now,Now),Up>>=1;
    return Ret;
}
void Divide()
{   for(int i=1;i<=Ls;i++)
    {   int Num=Seq[i],Now=Seq[i],Np,Nps;
        while(Now>1)
        {   Np=Minp[Now],Nps=0;
            while(Now%Np==0) Now/=Np,Nps++;
            Own[Np][Nps]++;
        }
    }
    for(int i=1;i<=Ps;i++)
        for(int j=Top[i];j>=0;j--) Own[Pri[i]][j]+=Own[Pri[i]][j+1];
}
void Solve()
{   for(int i=1;i<=Ps;i++)
        for(int j=1;j<=Top[i];j++)
            Ans=Mul(Ans,Pow(Pri[i],Add(C[Own[Pri[i]][j]][K],Ep-C[Own[Pri[i]][j+1]][K])*j%Ep));
}
void Init()
{   Ans=1,Ep=1;
    for(int i=0;i<=Ps;i++)
        for(int j=0;j<=Top[i];j++) Own[Pri[i]][j]=0;
}
void Get_Pri()
{   for(int i=2;i<=1e7;i++)
    {   if(!Notp[i]) Pri[++Ps]=i,Minp[i]=i;
        for(int j=1;j<=Ps;j++)
        {   if(Pri[j]*i>1e7) break ;
            Notp[Pri[j]*i]=1,Minp[Pri[j]*i]=Pri[j];
            if(i%Pri[j]==0)  break ;
        }
    }
    Pps=Ps,Ps=1;
    while(Pri[Ps+1]<80000) Ps++;
    for(int i=1;i<=Ps;i++)
    {   int K=80000;
        while(K>1) Top[i]++,K/=Pri[i];
    }
}
int main()
{   Get_Pri();
    for(T=Read();T;T--)
    {   Ls=Read(),K=Read(),scanf("%lld",&P);
        for(int i=1;i<=Ls;i++) Seq[i]=Read();
        Init(),Euler(),Combine(),Divide(),Solve(),printf("%lld\n",(ll)Ans);
    }
}
posted @ 2021-07-20 20:49  Alkaid~  阅读(233)  评论(0编辑  收藏  举报