第十一届中国大学生程序设计竞赛 重庆站(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);
  }
}
posted @ 2026-09-10 13:37  xx019  阅读(57)  评论(0)    收藏  举报