2026杭电暑期 6

怎么我写的都不是正解?

L

赛后发现数据最水的一集

考虑到无穷回合之后树上的每一个点,其 \(A_i\) 必然会被贡献到根

所以如果 \(\sum A_i\) 不为 \(0\) 的话,

这些无穷轮的贡献一定可以看作 \(\sum A_i\) 加到根上无穷轮

那么根的权值符号就取决于 \(\sum A_i\)

那么考虑 \(\sum A_i=0\) 的情况,这个时候就需要手动模拟了

所以我手动模拟了一轮,然后过了

不过手动模拟太慢了,有没有更快的方式算出这一部分的权值呢?

有的,因为某个点推了一轮贡献到根之后,每一轮都会有这个点推的贡献

所以直接算出每个点贡献到根的最早回合即可,这样就可以很快算出贡献了

J

推出来了两个结论

第一个结论是,

假设一个蓝牌放到了一个红牌前面,那么它的伤害增幅是一个固定且按照等差递减的数值

第二个结论是,

如果一个蓝牌放到两个放有相同数量蓝牌的红牌之前,那么这个蓝牌无论放到哪个红牌前,伤害增幅不变

那么说明蓝牌的分配是要尽可能均分

后面的推式子和代码都是左老师写的,因为我最后的式子不对,所以我不继续写了

不过莽了交了点罚时出来

H

H是罗老师的结论题,暂时放下

B

B是模拟,暂时放下

C

C 的正解是狄利克雷前缀 max 状物

这里就可以左拐去狄利克雷前缀和板子

我们首先从定义展开,则有 \(\varphi(x)=x\prod\limits_{p\in P}\frac{p_i-1}{p_i}\)

那么对于 \(x\cdot a_k\) 后面的部分就可以拆成 \(x\) 单独贡献的,\(a_k\) 单独贡献的和共有的只算一次的部分

那么把共有的部分划给 \(x\) 剩下的部分可以看作是 \(\frac{a_k}{\text{gcd}(a_k,x)}\) 贡献的,

\(\text{gcd}(a_k,x)=g\)

则得到 \(\varphi(x\cdot a_k)=\varphi(x)\cdot g\varphi(\frac{a_k}{g})\)

那么要求最大值,自然是求后半部分的最大值

这个时候常见的技巧是把最大公约数化成公约数,类似莫比乌斯反演

正确性在于,如果假设 \(d|g\) 那么则会因为 \(\frac{a_k}{d}\) 会贡献出更多的质因子

那么这里,更多的质因子会在贡献中实际被减一乘进去,所以其实不优

这样的话就转化成了求 \(f(x)=\varphi(x)\max\limits_{i|x} (i\varphi (\frac{a_k}{i}))\)

后面的其实也是可以用狄利克雷前缀和更新的,只不过需要倒写,

前面更是板子,单组时间复杂度与埃式筛一致 \(O(V\log\log V)\)


那么如果不会狄利克雷前缀和呢?

考虑枚举上述提到的公共部分,记 \(\omega(x)\) 表示 \(x\) 质因式分解中,不同质因子构成的集合的大小

那么 \(x\leq 10^7\) 可知 \(\omega(x)\leq 8\)

公共部分的枚举量是 \(2^{\omega(x)}\) 实际上非常松啊

所以只需要找到公共部分,算出在 \(x\) 这边剩余部分的值

然后对于每一个 \(a_k\) 同样枚举这个公共部分,更新 \(max\) 即可

时间复杂度是 反正能过

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long 
const int o=1e7+22,N=2e6+22;
int pr[o],phi[o],n,T,cnt,p[o][8],ans[o],v[o];
int a[N];
int q[o],sta[(1<<8)+22],lg[256],fst[(1<<8)+22];
ll sz[10100];
void in(){
    cin>>n;
    for(int i=1;i<=n;i++)cin>>a[i];
}
void pre(int n){
    phi[1]=1;
    for(int i=2;i<=n;i++){
        if(v[i]==0){
            cnt++;
            v[i]=i;
            pr[cnt]=i;
            phi[i]=i-1;
            p[i][ans[i]]=i;
            ans[i]++;
        }
        for(int j=1,now;j<=cnt;j++){
            if(pr[j]>v[i]||pr[j]>n/i)break;
            now=i*pr[j];
            v[now]=pr[j];
            if(v[i]!=pr[j])p[now][ans[now]]=pr[j],ans[now]++;
            for(int k=0;k<ans[i];k++)p[now][ans[now]]=p[i][k],ans[now]++;
            phi[i*pr[j]]=phi[i]*(i%pr[j]?pr[j]-1:pr[j]);
        }
    }
    for(int i=2;i<256;i++)lg[i]=lg[i/2]+1;
}
void solve(){
    for(int i=1;i<=n;i++){
        int now=a[i];
        sta[0]=1;
        for(int j=0;j<(1<<ans[now]);j++){
            int lb=j&(-j);
            if(j!=0){
                int pos=lg[lb];
                sta[j]=sta[j^lb]*p[now][pos];
            }
            q[sta[j]]=max(phi[a[i]],q[sta[j]]);
        }
    }
    int x=1e7,B=1000;
    ll val=0;
    for(int i=1;i<=x;i++){
        sta[0]=1,fst[0]=i;
        for(int j=1;j<(1<<ans[i]);j++){
            int lb=j&(-j);
            int pos=lg[lb];
            sta[j]=sta[j^lb]*p[i][pos];
            fst[j]=fst[j^lb]/p[i][pos]*phi[p[i][pos]];
        }
        int now=(1<<ans[i])-1,pos=0;
        val=0;
        for(int j=0;j<(1<<ans[i]);j++){
            val=max(1ll*q[sta[j]]*fst[now^j],val);
        }
        now=(i+B-1)/B,pos=i%B;
        sz[pos]^=(1ll*now*val);
    }
    for(int i=0;i<1000;i++)cout<<sz[i]<<"\n";
}
void clear(){
    for(int i=1;i<=n;i++)a[i]=0;
    for(int i=1;i<=1e7;i++)q[i]=0;
    for(int i=0;i<=1e4;i++)sz[i]=0;
}
int main(){
    //freopen("C.in","r",stdin),freopen("C.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>T;pre(1e7);
    while(T--)in(),solve(),clear();
    return 0;
}

F

考虑先交换求和顺序,把最大的 \(j\) 拉到最前面,然后让 \(k\) 当第二个,

那么原式为 \(\sum\limits^{n}_{j=1}\sum\limits^{j}_{k=1}\sum\limits^{k}_{i=1}\text{gcd}(i,k)\text{gcd}(j,k)\binom{j}{k}\)

这样的话,最后和 \(j,k\) 相关的项可以先提出去,

则原式为 \(\sum\limits^{n}_{j=1}\sum\limits^{j}_{k=1}\text{gcd}(j,k)\binom{j}{k}\sum\limits^{k}_{i=1}\text{gcd}(i,k)\)

\(f(k)=\sum\limits^{k}_{i=1}\text{gcd}(i,k)\)

那么根据 \(\varphi*1=id\) (说人话就是 \(\sum\limits_{d|k}\varphi(d)=k\))

则有 \(\text{gcd}(i,k)=\sum\limits_{d|gcd(i,k)}\varphi(d)\),即为欧拉反演

依旧交换求和顺序,考虑一个 \(d\) 首先应该是 \(k\) 的因子才能做出贡献

然后考虑 \(d\) 必须是 \(i\)\(k\) 的公因子,这样一个 \(d\) 就可以给所有它的倍数做出贡献

那么一个 \(d\) 就可以贡献 \(\frac{k}{d}\) 次,

则有 \(f(k)=\sum\limits_{d|k}\frac{k}{d}\varphi(d)\)

现在原式为 \(\sum\limits^{n}_{j=1}\sum\limits^{j}_{k=1}\text{gcd}(j,k)\binom{j}{k}f(k)\)

接下来继续对 \(\text{gcd}\) 做欧拉反演,则有

\(\sum\limits^{n}_{j=1}\sum\limits^{j}_{k=1}\binom{j}{k}f(k)\sum\limits_{d|\text{gcd}(j,k)}\varphi(d)\)

这样的话还是一样的套路 \(\sum\limits_{d|\text{gcd}(j,k)}\varphi(d)\) 提到最前枚举倍数就可以

这样原式就成了 \(\sum\limits_{d=1}\varphi(d)\sum\limits^{n}_{j=d,d|j}\sum\limits^{j}_{k=d,d|k}\binom{j}{k}f(k)\)

只差最后一部分了,这一部分则是展开组合数,则有

\(\sum\limits^{n}_{j=d,d|j}\sum\limits^{j}_{k=d,d|k}\binom{j}{k}f(k)=\sum\limits^{n}_{j=d,d|j}j!\sum\limits^{j}_{k=d,d|k}\frac{1}{(j-k)!}\frac{f(k)}{k!}\)

注意到后半部分是个卷积的形式,那直接 NTT 就可以

NTT 挂一只 \(\log\) 然后枚举 \(d\) 的倍数导致的卷积项数是调和级数出一个 \(\log\)

总时间复杂度为 \(O(n\log^2 n)\)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long 
const int o=(1<<20)+10,gy=3,mod=998244353;
int n,T,ans;
int f[o],g[o],h[o],r[o],jc[o],inv[o];
int phi[o],lg[o];
int P(int x,int y,int m){
    int ans=1;
    while(y){
        if(y&1)ans=ans*x%m;
        x=x*x%m;
        y>>=1;
    }
    return ans;
}
void pre(int n){
    jc[0]=1;
    for(int i=1;i<=n;i++)jc[i]=jc[i-1]*i%mod;
    inv[n]=P(jc[n],mod-2,mod);
    for(int i=n-1;i>=0;i--)inv[i]=inv[i+1]*(i+1)%mod;
    for(int i=1;i<=n;i++)phi[i]=i;
    for(int i=2;i<=n;i++){
        if(phi[i]!=i)continue;
        for(int j=i;j<=n;j+=i)phi[j]=phi[j]/i*(i-1);
    }
    for(int i=1;i<=n;i++){
        for(int j=i;j<=n;j+=i)f[j]=(f[j]+(j/i)*phi[i]%mod)%mod;
    }
    for(int i=0;i<20;i++){
        for(int j=(1<<i)+1;j<=(1<<(i+1));j++)lg[j]=i+1;
    }
}
void NTT(int *a,int n,int val){
    n=1<<(lg[n]);
    for(int i=1;i<n;i++){
        r[i]=r[i/2]/2+(i&1)*(n/2);
    }
    for(int i=1;i<n;i++){
        if(i<r[i])swap(a[r[i]],a[i]);
    }
    for(int i=2;i<=n;i*=2){
        int gn=P(gy,(mod-1)/i,mod);
        for(int j=0;j<n;j+=i){
            for(int k=j,now=1;k<j+i/2;k++,now=now*gn%mod){
                int x=a[k],y=a[k+i/2];
                a[k]=(x+now*y%mod)%mod;
                a[k+i/2]=(x-now*y%mod+mod)%mod;
            }
        }
    }
    if(val==-1){
        reverse(a+1,a+n);
        int inv=P(n,mod-2,mod);
        for(int i=0;i<n;i++)a[i]=a[i]*inv%mod;
    }
}
void solve(){
    cin>>n;
    for(int d=1;d<=n;d++){
        int len=n/d+1,now=0;
        for(int k=1;k<=n/d;k++)g[k]=f[k*d]*inv[k*d]%mod;
        for(int k=0;k<n/d;k++)h[k]=inv[k*d]%mod;
        NTT(g,2*len,1),NTT(h,2*len,1);
        for(int j=0;j<(1<<lg[2*len]);j++)g[j]=g[j]*h[j]%mod;
        NTT(g,2*len,-1),NTT(h,2*len,-1);
        for(int k=1;k<len;k++)now=(now+jc[k*d]*g[k]%mod)%mod;
        ans=(ans+phi[d]*now%mod)%mod;
        for(int j=0;j<(1<<lg[2*len]);j++)g[j]=0,h[j]=0;
    }
    cout<<ans<<"\n";
    ans=0;
}
#undef int
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>T;pre(1<<20);
    while(T--)solve();
    return 0;
}

A

考虑答案的取值,结论是答案只能取 \(1\)\(2\)

posted @ 2026-08-09 00:21  2K22  阅读(8)  评论(0)    收藏  举报