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);
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM