NOIP2023模拟9联测30

发布时间 2023-11-03 17:01:33作者: Trmp

这篇博客是第二天赛时写的。(恼)

T1

数学题。

肯定是想把 \(k\) 质因数分解,然后找一找规律,发现对于一个最小的 \(n\) 一定不包括除了 \(k\) 有的质因子以外的其他质因子,因为其他质因子对是不是 \(k\) 的倍数没有用。

\(n^2\) 相当于把 \(n\) 的所有质因子的指数乘了个 \(2\), 那么只要保证这个指数乘 \(2\) 后大于等于 \(k\) 的相同的质因子的指数,那么就是满足条件,要保证最小,直接把 \(k\) 质因子指数除以二向上取整就好了。

发现数很大,没办法全部质因数分解。发现对于大于 \(\sqrt{k}\) 的质因子的指数一定不大于一,那直接算到 \(\sqrt{k}\), 剩下的直接乘就可以了。

记得判无解情况,即 \(n=k\) 时。

Code
#include <iostream>
#include <cstdio>

#define int long long

const int N=1e7+10;

using namespace std;

inline int read() {
    int f=1, x=0;
    char ch=getchar();
    while(ch>'9' || ch<'0') {
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9') {
        x=(x<<3)+(x<<1)+(ch^48);
        ch=getchar();
    }
    return f*x;
}

inline void write(int x) {
    cout<<x; putchar('\n');
}

int k, tot, ans;
int prime[N], is_prime[N], cnt[N];

void pre_work() {
    for(int i=2;i<=N-5;i++) {
        if(!is_prime[i]) prime[++tot]=i;
        for(int j=1;j<=tot && prime[j]*i<=N-5;j++) {
            is_prime[prime[j]*i]=1;
            if(i%prime[j]==0) break;
        }
    }
}

inline int get_(int x,int k) {
    if(x%k==0) return x/k;
    else return (x/k)+1;
} 

inline int fpow(int x,int k) {
    int res=1;
    while(k) {
        if(k&1) res*=x;
        x=x*x;
        k>>=1;
    }
    return res;
}

signed main() {

    pre_work();

    k=read();
    
    int now=k;
    for(int i=1;i<=tot && prime[i]<=now;i++) {
        if(now%prime[i]==0) {
            while(now%prime[i]==0) {
                cnt[i]++;
                now/=prime[i];
            }
        }
    }

    int ans=now;
    for(int i=1;i<=tot;i++) {
        if(cnt[i]) {
            ans=ans*fpow(prime[i], get_(cnt[i], 2));
        }
    }
    if(ans==k) write(-1);
    else write(ans);

    return 0;
}

T2

Part1

发现值域只有 \([1,9]\),那么直接分讨,对于 \(1, 5, 7\) 这三个数,和其他数的 \(gcd\) 都为 \(1\),又因为相同数没有区别,所以他们仨可以随便放。先分出来单独讨论。

Part2

剩下的 \(2,3,4,6,8,9\),发现他们的公共质因子只有 \(2,3\), 而 \(6\) 又都包含,说明 \(6\) 不能换,只能在这里,那么若干个 \(6\) 把序列分成了若干段,段与段之间互不影响。

考虑段内的贡献,发现公共质因子为 \(2\) 的几个数相对位置不会变化,为 \(3\) 的也不会变化,这两种数没有交集,互不影响,那么可以直接组合数算,设公共质因子为 \(2\) 的数有 \(n\) 个,为 \(3\) 的数有 \(m\) 个,那贡献就是 \(\dbinom{n+m}{n}\),最后全部乘起来就好了。

对于 \(1,5,7\) 的贡献,和上面差不多,设 \(1\) 的总数为 \(n\),当前数的个数为 \(m\),贡献即为 \(\dbinom{n+m}{n}\)\(5\)\(7\) 也是一样的。

复杂度为 \(\Theta(n)\)

Code
#include <iostream>
#include <cstdio>

#define int long long  

const int mod=998244353;
const int N=1e5+10;

using namespace std;

inline int read() {
    int f=1, x=0;
    char ch=getchar();
    while(ch>'9' || ch<'0') {
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9') {
        x=(x<<3)+(x<<1)+(ch^48);
        ch=getchar();
    }
    return f*x;
}

inline void write(int x) {
    cout<<x; putchar('\n');
}

int n, tot, ans;
int a[N], cnt[N];
int fac[N], inv[N], invj[N];

void prework() {
    fac[0]=fac[1]=inv[0]=inv[1]=invj[0]=invj[1]=1;
    for(int i=2;i<=n+5;i++) {
        fac[i]=fac[i-1]*i%mod;
        inv[i]=(mod-mod/i)*inv[mod%i]%mod;
        invj[i]=invj[i-1]*inv[i]%mod;
    }
    ans=1;
}

inline int C(int n,int m) {
    if(m>n) return 0;
    return fac[n]*invj[n-m]%mod*invj[m]%mod;
}

inline void solve(int l,int r) {
    if(l>=r) return;
    int sum1=0, sum2=0;
    for(int i=l;i<=r;i++) {
        if(a[i]%2==0) sum1++;
        else sum2++;
    }
    int sum=C(sum1+sum2, sum1);
    ans=(ans*sum)%mod;
}

signed main() {
    
    n=read();

    prework();

    for(int i=1;i<=n;i++) {
        int x=read();
        if(x==1 || x==5 || x==7) {
            cnt[x]++;
        }
        else a[++tot]=x;
    }
    
    int pre=1;
    for(int i=1;i<=tot;i++) {
        if(a[i]==6) {
            solve(pre, i-1);
            pre=i+1;
        }
    }
    solve(pre, tot);

    tot+=cnt[1], ans=(ans*C(tot, cnt[1]))%mod;
    tot+=cnt[5], ans=(ans*C(tot, cnt[5]))%mod;
    tot+=cnt[7], ans=(ans*C(tot, cnt[7]))%mod;

    write(ans);

    return 0;
}

T3

Part1

设最终的力量值为 \(x\),首先发现将 \(x\) 设置为原有的一条龙的力量值一定更优,证明考虑移动 \(x\),在龙与龙之间时的图像是一条直线,那么最值一定出在一个拐点上,也就是原有的一条龙的力量值。

考虑刻画出函数图像,对于单个一条龙,函数图像一定是一个凸函数,又因为凸函数也是凸函数,那么多条龙的函数的加和也一定是凸函数,那么可以直接三分找到最值,只不过复杂度为 \(\Theta(n\log^2n)\),好像可以过。

Part2

考虑那个凸函数各部分的斜率,发现越接近答案,斜率越小,可以通过这个直接求出答案拐点的排名。

设答案拐点前有 \(P\) 个点,那么从第 \(P\) 个点到第 \(P+1\) 个点的斜率一定是最小的,单位变化量也是最小的。想象一个点在图像上移动,每移动 \(1\) 的力量值,\(\Delta f(x)=a\times P-b\times(i-P) \leqslant 0\),解得 \(P \leqslant \dfrac{b\times i}{a+b}\), 这个 \(P\) 一定是最大的,所以取等,答案拐点的排名即为 \(P+1\)

Part3

代码实现上,先离散化,因为要动态查询排名为 \(K\) 的值,快速求出以 \(x\) 为最终的力量值的总代价,所以直接权值线段树就行了,然后一个一个地加,一个一个地求,复杂度 \(\Theta(n\log n)\)

Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <algorithm>

#define int long long

const int N=1e6+10;

using namespace std;

inline int read() {
    int f=1, x=0;
    char ch=getchar();
    while(ch>'9' || ch<'0') {
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9') {
        x=(x<<3)+(x<<1)+(ch^48);
        ch=getchar();
    }
    return f*x;
}

inline void write(int x) {
    cout<<x; putchar('\n');
}

int n, a, b;
int d[N], mp[N], ans[N];

map <int,int> Kth;

 struct Tree {
        int sum, size;
    }t[N<<2];

struct Seg_Tree {

    inline Tree merge(Tree ls, Tree rs) {
        Tree res=(Tree){0, 0};
        res.size=ls.size+rs.size;
        res.sum=ls.sum+rs.sum;
        return res;
    }
   
    inline void push_up(int k) {
        t[k]=merge(t[k<<1], t[k<<1|1]);
    }

    void change(int k,int l,int r,int pos,int val) {
        if(l==r) {
            t[k].sum+=val*mp[l];
            t[k].size+=val;
            return;
        }
        int mid=(l+r)>>1;
        if(pos<=mid) change(k<<1, l, mid, pos, val);
        else change(k<<1|1, mid+1, r, pos, val);
        push_up(k);
    }

    int find(int k,int l,int r,int K) {
        if(l==r) return l;
        int mid=(l+r)>>1;
        if(t[k<<1].size>=K) return find(k<<1, l, mid, K);
        else return find(k<<1|1, mid+1, r, K-t[k<<1].size);
    }

    Tree query(int k,int l,int r,int L,int R) {
        if(L<=l && r<=R) return t[k];
        int mid=(l+r)>>1;
        Tree ls=(Tree){0, 0}, rs=(Tree){0, 0};
        if(L<=mid) ls=query(k<<1, l, mid, L, R);
        if(R>mid) rs=query(k<<1|1, mid+1, r, L, R);
        return merge(ls, rs);
    }
}T;

signed main() {

    n=read(), a=read(), b=read();
    for(int i=1;i<=n;i++) {
        d[i]=read();
        mp[i]=d[i];
    }

    sort(mp+1, mp+n+1);
    int Len=unique(mp+1, mp+n+1)-mp-1;
    for(int i=1;i<=Len;i++) Kth[mp[i]]=i;

    for(int i=1;i<=n;i++) {
        T.change(1, 1, n, Kth[d[i]], 1);
        int p=(b*i)/(a+b);
        int pos=T.find(1, 1, n ,p+1);
        Tree ll=(Tree){0, 0}, rr=(Tree){0, 0};
        if(pos-1>=1) ll=T.query(1, 1, n, 1, pos-1);
        if(pos+1<=n) rr=T.query(1, 1, n ,pos+1, n);
        ans[i]=(mp[pos]*ll.size-ll.sum)*a+(rr.sum-mp[pos]*rr.size)*b;
    }
    
    for(int i=1;i<=n;i++) write(ans[i]);

    return 0;
}

T4

\(T4\) 的难度有点水吧?

Part1

根据初中数学知识:

\(a^x·a^y=a^{x+y}\)

\((a_1+a_2+\dots+a_n)·(b_1+b_2+\dots+b_m)=a_1b_1+a_1b_2+\dots+a_1b_m+a_2b_1+a_2b_2+\dots+a_nb_m\)

那么可以直接把贡献拆开,\(u\) 的一部分,\(v\) 的一部分,\(u\)\(v\) 之间的路径。最后直接乘起来就行了。

Part2

\(f_i=\sum_{u\in sutree_i}2^{dis_{u,i}}\),$ g_i=\sum_{u=1}^{n}dis_{u,i}\(。\)f_i$ 就是它的子树内的点对他的贡献,\(g_i\) 是所有点对它的贡献。

分为两种情况:

第一种情况,\(u\)\(v\) 都不是 \(lca\),那么经过 \(u\)\(v\) 的路径的两个端点一定一个在 \(u\) 的子树,一个在 \(v\) 的子树,那么答案就是 \(f_u\times f_v \times 2^{dis_u,v}\),这个好算。

第二种情况,\(u\)\(v\) 其中有一个是 \(lca\),默认非 \(lca\) 的那个点为 \(u\),那么经过 \(u\)\(v\) 的路径的两个端点,一个在 \(u\) 子树内,另一个在 把 \(u\) 所在的 \(lca\) 的子树的所有节点刨除后,剩下的其他节点上,画一画图就明白了,我们把 \(u\) 所在的 \(lca\) 的子树的根节点称为 \(clca\),那么答案就是 \(f_u \times (g_{lca}-2\times f_{clca} )\times 2^{dis_{u,lca}}\)

Part3

实现上,可以用 换根\(dp\)\(\Theta(n)\) 复杂度内求出 \(f_i\)\(g_i\)。(也可以用点分治,只不过复杂度劣一些,\(\Theta(n\log n)\) 加上大常数,可能过不去,而且还相对难写)

\(lca\)\(clca\) 可以用树剖(倍增常数太大),也可以用 \(Tarjan\) 离线求。

如何用树剖求 \(clca\),先找到 \(lca\),然后从 \(u\)\(lca\) 跳,记录当前链的链顶,跳到链顶相同后开始分讨。

  • \(u=lca\),也就是上一条链的链顶的父亲是 \(lca\)\(clca\)\(lca\) 不在同一条重链上,那么上一条链的链顶就是 \(clca\)

  • 剩下的情况就是 \(clca\)\(lca\) 在同一条重链上,因为重链上的 \(dfs\)序 是连续的,那么 \(clca\)\(dfs\)序 就是 \(lca\) 的下一位。预处理时记录一下 \(dfs\)序 就可以了。

复杂度为 \(\Theta(q\log n)\),轻微卡常。

Code
#include <iostream>
#include <cstdio>

#define rint register int
#define ll long long

const int N=1e6+10;
const ll mod=998244353;

using namespace std;

inline int read() {
    int f=1, x=0;
    char ch=getchar();
    while(ch>'9' || ch<'0') {
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9') {
        x=(x<<3)+(x<<1)+(ch^48);
        ch=getchar();
    }
    return f*x;
}

inline void write(ll x) {
    cout<<x; putchar('\n');
} 

int n, cnt_edge;
struct edge {
    int next ,to;
}e[N<<1];
int head[N];

inline void add_edge(int u,int v) {
    e[++cnt_edge].to=v;
    e[cnt_edge].next=head[u];
    head[u]=cnt_edge;
}

ll vs[N], vr[N];
void dfs1(int now,int fa) {
    vs[now]=1;
    for(rint i=head[now];i;i=e[i].next) {
        int v=e[i].to;
        if(v==fa) continue;
        dfs1(v, now);
        vs[now]=(vs[now]+vs[v]*2ll)%mod;
    }
}

void dfs2(int now,int fa) {
    ll sum=(vr[fa]+mod-vs[now]*2ll)%mod;
    vr[now]=(vs[now]+sum*2ll)%mod;
    for(rint i=head[now];i;i=e[i].next) {
        int v=e[i].to;
        if(v==fa) continue;
        dfs2(v, now);
    }
}

int tim, dfn[N], mp[N];
int dep[N], siz[N], fa[N], top[N], son[N];
struct Heavy {
    void dfs1(int now,int father) {
        dep[now]=dep[father]+1, siz[now]=1;
        fa[now]=father, son[now]=0;
        int maxson=-1;
        for(rint i=head[now];i;i=e[i].next) {
            int v=e[i].to;
            if(v==father) continue;
            dfs1(v, now);
            siz[now]+=siz[v];
            if(siz[v]>maxson) {
                maxson=siz[v], son[now]=v;
            }
        }
    }
    void dfs2(int now,int topf) {
        dfn[now]=++tim, mp[tim]=now;
        top[now]=topf;
        if(son[now]) dfs2(son[now], topf);
        for(rint i=head[now];i;i=e[i].next) {
            int v=e[i].to;
            if(v==son[now] || v==fa[now]) continue;
            dfs2(v, v);
        }
    }
   
    inline int LCA(int x,int y) {
        while(top[x]!=top[y]) {
            if(dep[top[x]] < dep[top[y]]) x^=y, y^=x, x^=y;
            x=fa[top[x]];
        }
        if(dep[x] > dep[y]) x^=y, y^=x, x^=y;
        return x;
    }

    inline int CLCA(int x,int y) {
        int topf=0;
        while(top[x]!=top[y]) {
            topf=top[x];
            x=fa[top[x]];
        }
        if(x==y) return topf;
        else if(fa[x]==y) return x;
        else return mp[dfn[y]+1];
    }
}H;

ll power[N];
void prework() {
    dfs1(1, 0);
    vr[1]=vs[1];
    for(rint i=head[1];i;i=e[i].next) {
        int v=e[i].to;
        dfs2(v, 1);
    }
    power[0]=1;
    for(rint i=1;i<=n+5;++i) {
        power[i]=power[i-1]*2ll%mod;
    }
    H.dfs1(1, 0);
    H.dfs2(1, 1);
}

signed main() {
    
    n=read();
    for(rint i=1;i<n;++i) {
        int u=read(), v=read();
        add_edge(u, v);
        add_edge(v, u);
    }

    prework();

    int q=read();
    for(rint tur=1;tur<=q;++tur) {
        int u=read(), v=read();
        int lca=H.LCA(u, v);
        int dis=dep[u]+dep[v]-2ll*dep[lca];
        ll ans=power[dis];
        if(u==lca || v==lca) {
            if(u==lca) u=v;
            int clca=H.CLCA(u, lca);
            ans=(ans*vs[u]%mod*((vr[lca]-vs[clca]*2ll+mod)%mod))%mod;
        }
        else ans=ans*vs[u]%mod*vs[v]%mod;
        write((ans+mod)%mod);
    }
    
    return 0;
}