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