第十一届中国大学生程序设计竞赛 重庆站(CCPC 2025 Chongqing Site) 题解
A. New Gomoku
题意:有一个 1000*1000 的棋盘,两个人分别执黑和执白,每次下子后询问此人所代表的颜色的棋子能形成多少个横向/纵向/斜向的 5 个棋子。\(\sum n\le 10^4\)。
做法:签到。需要注意的是比如有 6 个连在一起的黑子则会对答案产生 2 的贡献。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2000+5;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int a[N][N],as[5];
void sol(){
int n=read();
vector<pair<int,int> >pos;
for(int i=1,o=1;i<=n;i++,o=3-o){
int x=read(),y=read();a[x][y]=o;
pos.push_back({x,y});
for(int j=x-4;j<=x;j++){
int F=1;
for(int k=j;k<=j+4;k++){
if(k<1||k>1000){F=0;break;}
if(a[k][y]!=o){F=0;break;}
}
as[o]+=F;
}
for(int j=y-4;j<=y;j++){
int F=1;
for(int k=j;k<=j+4;k++){
if(k<1||k>1000){F=0;break;}
if(a[x][k]!=o){F=0;break;}
}
as[o]+=F;
}
for(int j=-4;j<=0;j++){
int F=1,p=x+j,q=y+j;
for(int k=0;k<=4;k++){
if(p+k<1||p+k>1000||q+k<1||q+k>1000){F=0;break;}
if(a[p+k][q+k]!=o){F=0;break;}
}
as[o]+=F;
}
for(int j=-4;j<=0;j++){
int F=1,p=x+j,q=y-j;
for(int k=0;k<=4;k++){
if(p+k<1||p+k>1000||q-k<1||q-k>1000){F=0;break;}
if(a[p+k][q-k]!=o){F=0;break;}
}
as[o]+=F;
}
printf("%lld ",as[o]);
}
for(auto [x,y]:pos)a[x][y]=0;
pos.clear();
as[0]=as[1]=as[2]=0;
}
signed main(){
int T=read();while(T--)sol();
return 0;
}
B. Abstract Portal
题意:给定一个长为 k-1 的序列 \(t_1\ldots t_{k-1}\),点 0 到 n 都有一个初始为 0 的变量 \(c_i\),你需要重复以下操作直到第一次从位置 0 移动到位置 n:
假设当前在位置 i,你需要让 \(c_i\gets (c_i+1)\bmod k\)。若 \(c_i\) 在此次操作后变为 0 则你传送到 i+1,否则若 \(i\ge t_{c_i}\),则你传送到 \(i-t_{c_i}\),否则无事发生。
求最终的传送次数。\(n\le 10^9,2\le k\le 1000,0\le t_i\le\min\{100,n-1\}\),答案对 \(10^9+7\) 取模。
做法:设 \(f_{i,j}\) 表示从 i 出发走到 j 且让 \(c_j\) 刚好达到 k-1 的传送次数。假设已经求出 \(f_{*,i-1}\),考虑转移到 \(f_{*,i}\):\(f_{i,i}=\sum_{j=1}^{k-1}[t_j=0]+[t_j\neq 0\land i\ge t_j](f_{i-t_j,i-1}+2)\),\(f_{j,i}=f_{j,i-1}+1+f_{i,i}\),可以做到 \(O(n^2+nk)\),最终答案为 \(f_{0,n-1}+1\)。注意到 \(f_{i,i}\) 的转移仅需要 \(f_{j,i-1},j\in[i-t,i-1]\) 内的值,考虑矩阵乘法,复杂度 \(O(T^3\log n)\),可以通过。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=100+5,K=1000+5,P=1e9+7;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct mat{
int a[N][N];
mat(){memset(a,0,sizeof(a));}
mat operator *(const mat b)const{
mat c;
for(int i=0;i<=101;i++){
for(int j=0;j<=101;j++){
for(int k=0;k<=101;k++){
(c.a[i][j]+=1ll*a[i][k]*b.a[k][j]%P)%=P;
}
}
}
return c;
}
};
mat qpow(mat x,int y){
mat rs;
for(int i=0;i<=101;i++)rs.a[i][i]=1;
while(y){
if(y&1ll)rs=rs*x;
x=x*x;y>>=1;
}
return rs;
}
int f[N],g[N],t[K];
void sol(){
int n=read(),m=read();
for(int i=1;i<m;i++)t[i]=read();
if(n<=100){
f[0]=0;
for(int i=1;i<m;i++)if(t[i]==0)(f[0]+=1)%=P;
for(int i=0;i<=n;i++)swap(f[i],g[i]),f[i]=0;
for(int j=1;j<n;j++){
f[j]=0;
for(int i=1;i<m;i++){
if(t[i]==0)(f[j]+=1)%=P;
if(t[i]>0&&j-t[i]>=0)(f[j]+=g[j-t[i]]+1+1)%=P;
}
for(int i=j-1;i>=0;i--){
f[i]=(g[i]+1+f[j])%P;
}
for(int i=0;i<=n;i++)swap(f[i],g[i]),f[i]=0;
}
printf("%d\n",(g[0]+1)%P);
return;
}
f[0]=0;
for(int i=1;i<m;i++)if(t[i]==0)(f[0]+=1)%=P;
for(int i=0;i<=100;i++)swap(f[i],g[i]),f[i]=0;
for(int j=1;j<=100;j++){
f[j]=0;
for(int i=1;i<m;i++){
if(t[i]==0)(f[j]+=1)%=P;
if(t[i]>0&&j-t[i]>=0)(f[j]+=g[j-t[i]]+1+1)%=P;
}
for(int i=j-1;i>=0;i--){
f[i]=(g[i]+1+f[j])%P;
}
for(int i=0;i<=100;i++)swap(f[i],g[i]),f[i]=0;
}
mat trans;
for(int i=1;i<m;i++){
if(t[i]==0)(trans.a[101][0]+=1)%=P;
if(t[i]>0)(trans.a[t[i]-1][0]+=1)%=P,(trans.a[101][0]+=2)%=P;
}
for(int i=1;i<=99;i++){
for(int j=0;j<=101;j++)trans.a[j][i]=trans.a[j][0];
(trans.a[i-1][i]+=1)%=P,(trans.a[101][i]+=1)%=P;
}
for(int j=0;j<=101;j++)trans.a[j][100]=trans.a[j][0];
(trans.a[100][100]+=1)%=P,(trans.a[101][100]+=1)%=P;
trans.a[101][101]=1;
mat st;
for(int i=0;i<=99;i++)st.a[0][i]=g[100-i];
st.a[0][100]=g[0],st.a[0][101]=1;
printf("%d\n",((st*qpow(trans,n-101)).a[0][100]+1)%P);
}
signed main(){
// freopen("input.in","r",stdin);
// freopen("std.out","w",stdout);
int T=1;while(T--)sol();
return 0;
}
E. Dinner Invitation
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::vector<std::array<i64, 3>> a(n);
std::vector<std::array<i64, 2>> w(n);
for (int i = 0; i < n; i++) {
std::cin >> a[i][0];
w[i][0] = a[i][0];
w[i][1] = a[i][2] = i;
}
for (int i = 0; i < n; i++) {
std::cin >> a[i][1];
}
std::sort(w.begin(), w.end(), [](auto& x, auto& y) -> bool {
return x[0] < y[0];
});
std::sort(a.begin(), a.end(), [](auto& x, auto& y) -> bool {
// return x[1] < y[1];
return x[1] == y[1] ? x[0] > y[0] : x[1] < y[1];
});
std::vector<i64> p(n);
for (int i = 0; i < n; i++) {
p[w[i][1]] = i;
}
for (int i = 0; i < n; i++) {
a[i][2] = p[a[i][2]];
}
i64 ans = 0;
i64 sum = 0, cnt = 0;
for (int i = 0, j = -1; i < n; i++) {
while (j + 1 < n) {
if (w[j + 1][0] == 0) {
j++;
continue;
}
i64 n_sum = sum + w[j + 1][0];
i64 n_cnt = cnt + 1;
if (n_sum <= n_cnt * a[i][1]) {
sum = n_sum;
cnt = n_cnt;
j++;
} else {
break;
}
}
ans = std::max(ans, cnt);
if (a[i][2] <= j) {
sum -= a[i][0];
cnt -= 1;
} else {
w[a[i][2]][0] = 0;
}
}
std::cout << ans << '\n';
}
int main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
int t;
std::cin >> t;
while (t --> 0) {
solve();
}
}
F. Turtle Soup
队友强强。
题意:定义 a 知道 b 当且仅当 b 是 a 的祖先(包括 a 自己)或 b 是 a 的儿子。对于 \(m\in[1,n]\),有另一个点 p 满足 m 不知道 p。现在有两个人 A/B 在竞争,B 可以选定 p 的位置,A 每次可以花费 \(dis(x,i)\) 的代价询问 p 是否知道 i,然后让 x 变为 i,初始时 x 为 m。当某次操作中 p=i 时则游戏结束。现在 A/B 分别想让代价最小/最大,求两人采取最优策略时每个 m 对应的代价。\(n\le 10^5\)。
做法:首先 p 不在 m 的祖先或儿子。开局我们会先问一次 i=m 以确定 p 是否在 m 的子树内。
-
若 p 知道 m 则答案为 \(f_{m,0}\)。定义 \(f_{u,0/1}\) 表示 x=u,且 u 的儿子不能/能成为 p 的代价。则 \(f_{u,1}=\max_{v 是 u 的第 i 个儿子}\{f_{v,1}+2i-1\},f_{u,0}=\max_{v 是 u 的第 i 个儿子且 v 不是叶子}\{f_{v,1}+2i-1\}\),要让最大值最小显然将 \(f_{v,1}\) 从大到小排序即可。
-
若 p 不知道 m 则 p 在 m 子树外。即 t=lca(p,m),则答案为 \(\max_t\{dis(m,t)+g_{s}\}\),其中 s 是 m 的祖先且 s 是 t 的儿子,且 \(g_u\) 表示已知 p 在 \(fa_u\) 子树内但不在 u 子树内且当前 \(x=fa_u\) 的答案。则如果 \(fa_u\) 只有 u 一个儿子就不可能,否则就去掉 u 这个儿子后类比一下 f 的求解过程即可。
复杂度 \(O(n\log n)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5,INF=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int f[N][2],g[N],pa[N],d[N];vector<int>G[N];
void dfs1(int u){
f[u][0]=f[u][1]=0;
for(auto v:G[u])d[v]=d[u]+1,dfs1(v);
sort(G[u].begin(),G[u].end(),[](int x,int y){
return f[x][1]>f[y][1];
});
for(int i=0,c1=0,c0=0;i<G[u].size();i++){
int v=G[u][i];
if(G[v].empty())c1++;
else c1++,c0++;
f[u][1]=max(f[u][1],f[v][1]+c1*2-1);
f[u][0]=max(f[u][0],f[v][1]+c0*2-1);
}
}
int a[N],pre[N],suf[N];
void dfs2(int u){
sort(G[u].begin(),G[u].end(),[](int x,int y){
return f[x][1]>f[y][1];
});
int sz=G[u].size();
for(int i=0;i<sz;i++)a[i+1]=f[G[u][i]][1];
pre[0]=-INF;for(int i=1;i<=sz;i++)pre[i]=max(pre[i-1],a[i]+2*i-1);
suf[sz+1]=-INF;for(int i=sz;i>=1;i--)suf[i]=max(suf[i+1],a[i]+2*i-1);
for(int i=0;i<sz;i++){
int v=G[u][i];
if(!pa[u]){
g[v]=max(pre[(i+1)-1],suf[(i+1)+1]-2);
}
else{
if(G[u].size()==1)g[v]=-INF;
else{
g[v]=max({pre[(i+1)-1],suf[(i+1)+1]-2});
}
}
}
for(auto v:G[u])dfs2(v);
}
int as[N];multiset<int>s;
void dfs3(int u){
if(u!=1)s.insert(-(g[u]-d[u]+1));
as[u]=f[u][0];
if(!s.empty())as[u]=max(as[u],-(*s.begin())+d[u]);
for(auto v:G[u])dfs3(v);
if(u!=1)s.erase(s.lower_bound(-(g[u]-d[u]+1)));
}
void sol(){
int n=read();
for(int i=2;i<=n;i++)pa[i]=read(),G[pa[i]].push_back(i);
dfs1(1);dfs2(1);dfs3(1);
for(int i=1;i<=n;i++)printf("%lld ",as[i]);
puts("");
}
signed main(){
int T=1;while(T--)sol();
return 0;
}
H. Hot Pot
队友强强。
题意:多测,数据组数 T。有三种人,分为只能吃辣/只能吃不辣/都能吃。现在有三种火锅:红汤/清汤/鸳鸯锅,每个火锅都能使得满足条件的至多两个人吃饱,求让所有人吃饱的最小花费。\(a,b,c,x,y,z,T\le 2025\)。
做法:枚举鸳鸯锅次数即可。复杂度 \(O(Tn)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int INF=1e18;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
int calc(int x,int y,int z,int a,int b,int c,int t){
int t1=t,t2=t,tmp;
tmp=min(x,t1),x-=tmp,t1-=tmp;
tmp=min(y,t2),y-=tmp,t2-=tmp;
tmp=min(t1,z),z-=tmp,t1-=tmp;
tmp=min(t2,z),z-=tmp,t2-=tmp;
if(!x&&!y&&!z)return 0;
int sum=0;
sum+=((x+1)/2)*a;
if(x&1ll)tmp=min(1ll,z),z-=tmp;
sum+=((y+1)/2)*c;
if(y&1ll)tmp=min(1ll,z),z-=tmp;
sum+=((z+1)/2)*min(a,c);
return sum;
}
void sol(){
int x=read(),y=read(),z=read(),a=read(),b=read(),c=read(),as=INF;
for(int i=0;i<=5000;i++)as=min(as,calc(x,y,z,a,b,c,i)+i*b);
printf("%lld\n",as);
}
signed main(){
int T=read();while(T--)sol();
return 0;
}
J. Tetris
题意:给定一个序列 \(f_{1}\ldots f_m\) 对应无穷序列 a 的一部分 \(a_{x+1}\ldots a_{x+m}\),其中 \(f_i>0\) 的位置满足 \(a_{i+x}=f_i\),否则 \(a_{i+x}\) 的值可以任取。要求序列 a 满足 \([kn+1,(k+1)n],k\in\mathbb{N}\) 内没有相同的值,求所有可能满足条件的 x 对 n 取模后的数量和异或和。有 q 次对 f 的单点修改。\(n\le 10^{18},m,q\le 5\times 10^5\)。
做法:首先只关心 [0,n) 的 x。任意相邻的两个值相同的位置可以产生一个 x 的限制,这个限制由至多两个区间组成,大大方方线段树即可。注意到当 n 很大时只会有很少的位置被限制覆盖,对 [n-m,n-1] 内线段树即可。复杂度 \(O((m+q)\log m)\)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M=5e5+5;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct segtree{
struct info{
int mx,c,s;
info operator +(const info t)const{
if(mx==t.mx)return {mx,c+t.c,s^t.s};
return (mx>t.mx)?(info){mx,c,s}:t;
}
}c[M<<2];
int tg[M<<2];
#define ls o<<1
#define rs o<<1|1
void up(int o){
c[o]=c[ls]+c[rs];
}
void down(int o){
c[ls].mx+=tg[o],c[rs].mx+=tg[o];
tg[ls]+=tg[o],tg[rs]+=tg[o];
tg[o]=0;
}
void build(int l,int r,int o){
tg[o]=0;
if(l==r){c[o]={0,1,l};return;}
int mid=(l+r)>>1;
build(l,mid,ls);build(mid+1,r,rs);
up(o);
}
void add(int l,int r,int o,int L,int R,int v){
if(L>R)return;
if(L<=l&&r<=R){tg[o]+=v,c[o].mx+=v;return;}
int mid=(l+r)>>1;down(o);
if(L<=mid)add(l,mid,ls,L,R,v);
if(R>mid)add(mid+1,r,rs,L,R,v);
up(o);
}
void print(int l,int r,int o){
printf("[%lld,%lld,%lld][%lld,%lld,%lld]\n",l,r,o,c[o].mx,c[o].c,c[o].s);
if(l==r)return;
int mid=(l+r)>>1;down(o);
print(l,mid,ls);print(mid+1,r,rs);
}
}tr;
int n,m,q,tot,all,L,R,a[M];set<int>s[M<<1];map<int,int>mp;
void ins(int l,int r){
if(r-l>=n)return;
int k=(r-1+n-1)/n,p=k*n-r+1,q=k*n-l;
if(q-p+1>=n)return;
all++;
if(q<n)tr.add(L,R,1,p,q,1);
else tr.add(L,R,1,p,n-1,1),tr.add(L,R,1,0,q-n,1);
}
void sub(int l,int r){
if(r-l>=n)return;
int k=(r-1+n-1)/n,p=k*n-r+1,q=k*n-l;
if(q-p+1>=n)return;
all--;
if(q<n)tr.add(L,R,1,p,q,-1);
else tr.add(L,R,1,p,n-1,-1),tr.add(L,R,1,0,q-n,-1);
}
void ins(int x){//a_x=y
if(!mp.count(a[x]))mp[a[x]]=++tot;
int I=mp[a[x]];
auto it=s[I].insert(x).first;
if(it!=s[I].begin())ins(*prev(it),x);
if(next(it)!=s[I].end())ins(x,*next(it));
if(it!=s[I].begin()&&next(it)!=s[I].end())sub(*prev(it),*next(it));
}
void sub(int x){//a_x=y
int I=mp[a[x]];
auto it=s[I].lower_bound(x);
if(it!=s[I].begin())sub(*prev(it),x);
if(next(it)!=s[I].end())sub(x,*next(it));
if(it!=s[I].begin()&&next(it)!=s[I].end())ins(*prev(it),*next(it));
s[I].erase(it);
}
int calc(int x){
if(x<0)return 0;
if(x%4==0)return x;
else if(x%4==1)return 1;
else if(x%4==2)return x+1;
else return 0;
}
int calc(int l,int r){
return (l<=r)?calc(r)^calc(l-1):0ll;
}
void print(){
if(n<=m){
if(tr.c[1].mx!=all)puts("0 0");
else printf("%lld %lld\n",tr.c[1].c,tr.c[1].s);
}
else{
if(tr.c[1].mx!=all)puts("0 0");
else if(all)printf("%lld %lld\n",tr.c[1].c,tr.c[1].s);
else printf("%lld %lld\n",n,calc(n-1));
}
}
void sol(){
n=read(),m=read(),q=read(),tot=0;mp.clear();
for(int i=1;i<=m;i++)a[i]=read();
if(n<=m)L=0,R=n-1;
else L=n-m,R=n-1;
tr.build(L,R,1);
for(int i=1;i<=m;i++)if(a[i])ins(i);
print();
while(q--){
int x=read(),y=read();if(a[x])sub(x);
a[x]=y;if(a[x])ins(x);
print();
}
}
signed main(){
int T=1;while(T--)sol();
return 0;
}
K. Reverse KMP
题意:给定数组 s 的 next 数组,并要求 \(s_i\in[1,m]\),求合法的 s 数组数。\(n\le 10^5,m\le 10^6\)。
做法:回忆我们 kmp 的过程,考虑连边 \((i,nxt_i)\) 建一个 next 树。注意到当 \(nxt_i=0\) 时说明 \(s_i\) 与 \(nxt_{i-1}\) 的祖先的每个节点 j 代表的 \(s_{j+1}\) 均不同;否则,设 \(nxt_i=k\),则 k-1 一定在 \(nxt_{i-1}\) 的祖先中,且 \(s_k=s_i\),k-1 和 \(nxt_{i-1}\) 之间的节点 j 代表的 \(s_{j+1}\) 与 \(s_i\) 均不同。前者只需要求出已经用过的颜色有多少种,后者颜色已经确定只需要判断是否合法即可。使用并查集解决,复杂度 \(O(n\log n)\)(因为只用了按秩合并)。
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5,M=1e6+5;
const int P=998244353;
inline int read(){
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
struct DSU{
int n,fa[N];
int find(int x){
return (x==fa[x])?x:fa[x]=find(fa[x]);
}
void merge(int x,int y){
if(find(x)!=find(y))fa[find(x)]=find(y);
}
void init(int _n){
n=_n;
for(int i=0;i<=n;i++)fa[i]=i;
}
}dsu;
int n,m,as,cnt,nxt[N],d[N],in[N];
vector<int>g[N],st[N],q[N];
void dfs1(int u){
for(auto v:g[u])d[v]=d[u]+1,dfs1(v);
}
void dfs2(int u){
in[u]=1;
if(u+1<=n){
if(st[dsu.find(u+1)].empty())cnt++;
st[dsu.find(u+1)].push_back(u);
}
for(auto v:q[u]){
int k=nxt[v],o=dsu.find(v);
if(k){
if(!in[k-1])as=0;
else if(!st[o].empty()&&d[st[o].back()]>d[k-1])as=0;
}
else{
if(m-cnt<=0)as=0;
else as=as*(m-cnt)%P;
}
}
for(auto v:g[u])dfs2(v);
in[u]=0;
if(u+1<=n){
st[dsu.find(u+1)].pop_back();
if(st[dsu.find(u+1)].empty())cnt--;
}
}
void sol(){
n=read(),m=read(),as=m;dsu.init(n);
for(int i=0;i<=n;i++)in[i]=d[i]=0;
for(int i=0;i<=n;i++)g[i].clear(),st[i].clear(),q[i].clear();
for(int i=1;i<=n;i++)nxt[i]=read();
for(int i=1;i<=n;i++)g[nxt[i]].push_back(i);
for(int i=1;i<=n;i++)if(nxt[i])dsu.merge(i,nxt[i]);
for(int i=2;i<=n;i++)q[nxt[i-1]].push_back(i);
dfs1(0);dfs2(0);printf("%lld\n",as);
}
signed main(){
int T=read();while(T--)sol();
return 0;
}
M. Reduction and Growth
队友强强。
题意:给定一个带点权 b 的初始仅有一个点 1 的树,对它进行 n-1 次操作。假设操作前有 m-1 个点,定义一次操作 (u,v,a) 表示令 \(b_m=a\),然后沿着树上从 u 到 v 的路径从 u 开始移动,经过一个点就让 \(b_m\gets \dfrac{b_m}{\gcd(b_m,b_i)}\),最后将 m 挂在 v 下,求 \(b_2\ldots b_n\)。
做法:注意到 \(b_i\) 的值只和它的每个质因子的次数有关,而这由只和路径上这个质因子的次数之和有关,于是主席树做完了,复杂度 \(O(n\log^2 n)\)。
点击查看代码
#include <bits/stdc++.h>
const int N = 2e7 + 5, M = 2e5 + 5;
int ls[N], rs[N], s[N];
int rt[M], cnt;
int c[M];
int fa[M][25], dep[M];
void ins(int p, int q, int l, int r, int x, int t) {
if (l == r) return s[q] = s[p] + t, void();
int mid = l + r >> 1;
ls[q] = ls[p], rs[q] = rs[p];
if (x <= mid) ins(ls[p], ls[q] = ++cnt, l, mid, x, t);
else ins(rs[p], rs[q] = ++cnt, mid + 1, r, x, t);
}
int qry(int p, int l, int r, int x) {
if (!p) return 0;
if (l == r) return s[p];
int mid = l + r >> 1;
return x <= mid ? qry(ls[p], l, mid, x) : qry(rs[p], mid + 1, r, x);
}
int lca(int u, int v) {
if (dep[u] < dep[v]) std::swap(u, v);
for (int i = 24; i >= 0; i--) if (dep[fa[u][i]] >= dep[v]) u = fa[u][i];
if (u == v) return u;
for (int i = 24; i >= 0; i--) if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
return fa[u][0];
}
const int K = 1e6 + 5;
std::vector<int> p;
int np[K], id[K];
int main() {
int n, a; scanf("%d %d", &n, &a); printf("%d ", a);
dep[1] = cnt = rt[1] = 1;
for (int i = 2; i <= 1e6; i++) if (!np[i]) {
p.push_back(i), id[i] = p.size();
for (int j = i; j <= 1e6; j += i) np[j] = 1;
}
int l = p.size();
auto solve = [&](int i, int x, int t) {
int r = ++cnt;
ins(rt[i], r, 1, l, id[x], t);
rt[i] = r;
// printf("+ %d %d %d\n", i, x, t);
};
for (auto x : p) {
if (1ll * x * x > a) break;
if (a % x == 0) {
int tt = 0;
while (a % x == 0) ++tt, a /= x;
int r = ++cnt;
solve(1, x, tt);
}
}
if (a > 1) solve(1, a, 1);
c[1] = a;
for (int i = 2; i <= n; i++) {
int a, u, v; scanf("%d %d %d", &a, &u, &v);
rt[i] = ++cnt;
ls[rt[i]] = ls[rt[v]], rs[rt[i]] = rs[rt[v]];
int res = 1;
auto calc = [&](int x, int tt) {
int r = ++cnt;
int t = qry(rt[v], 1, l, id[x]) + qry(rt[u], 1, l, id[x]);
int w = lca(u, v);
t -= qry(rt[w], 1, l, id[x]);
if (fa[w][0]) t -= qry(rt[fa[w][0]], 1, l, id[x]);
// printf("i=%d x=%d t=%d tt=%d\n", i, x, t, tt);
tt = std::max(tt - t, 0);
if (tt) solve(i, x, tt);
while (tt--) res *= x;
};
for (auto x : p) {
if (1ll * x * x > a) break;
if (a % x == 0) {
int tt = 0;
while (a % x == 0) ++tt, a /= x;
calc(x, tt);
}
}
if (a > 1) calc(a, 1);
fa[i][0] = v;
for (int j = 1; j < 25; j++) fa[i][j] = fa[fa[i][j - 1]][j - 1];
dep[i] = dep[v] + 1;
printf("%d ", res);
}
}

浙公网安备 33010602011771号