APIO2026 总结

考试过程

65+15+40=120

一开始还是准备按顺序看题,T1 发现是比较擅长的计数题就一直在思考,但是想了 1.5h 发现一点思路都没有,这个时候心态就有点受影响准备先去想后面的题目。然后发现 T2 是很长的交互题就以为比较难,T3 是数据结构就准备先想 T3。

T3 很快会了 40 分的暴力做法,这个时候发现可以分块做到 \(\mathcal{O}(n\sqrt n\log n)\),然后当时心态比较受影响,就想先写了看能拿多少分,而不是仔细分析。结果写了之后发现和暴力同分,又尝试优化了很久依然没有结果,就像 2025 年省选做追忆一样,对着一个跟暴力一样的做法瞪了很久。大概 3.5h 的时候准备再去想 T1,然后会了 \(\mathcal{O}(n^4)\) 的做法,写完之后只有 20min 去做 T2 了,最后只获得了 15 分。

总结

这次考试主要问题在于每个题目没有花足够的时间去思考,如果 T2 仔细想会发现其实是道比较简单的题目。T3 在想了一个不太对的复杂度之后应该先分析下能拿多少分再去写,而不是写完之后发现只有暴力分再一直卡常。最后 T1 计数题容斥的 trick 还不熟练需要多练习。

造成这样的主要原因可能是,之前考试我基本上都能很快做出 T1,而这次 T1 想了很久都没有思路,并且想到还是自己擅长的计数心态就受了很大影响,导致后面的节奏都被影响了。

题解

T1 题解:

第一想法是钦定一些位置上升,但是这样一个上升段可能包含很多个已经有的值,这样每个位置的限制比较多就很麻烦。考虑钦定一些位置下降,贡献为 \((1-w_i)\),如果不钦定贡献为 \(w_i\),钦定后将序列划分为若干个下降段,那么每个下降段最多包含一个有值的位置。

\(lst_i\) 表示 \(i\) 前面第一个有值的 \(a_j\) 是多少,如果没有就是 \(0\)。设 \(f_{i,j}\) 表示前 \(i\) 个数中有 \(j\)\(\le lst_i\) 的数的方案数,注意我们确定了 \(\le lst_i\) 的数的具体大小,确定了 \(>lst_i\) 的数的相对大小。

每次加一段 \([i+1,k]\),如果这段中有一个有值的位置那么可以 \(\mathcal{O}(1)\) 转移。否则还要枚举一个 \(>lst_i\)\(<lst_i\) 的断点,总复杂度为 \(\mathcal{O}(n^4)\)。可以发现枚举断点可以分步转移,于是复杂度优化到 \(\mathcal{O}(n^3)\)

#include <bits/stdc++.h>
// #include "ascend.h"
// #include "grader.cpp"
#define ll long long
using namespace std;
const int N = 505;
int a[N],lst[N],n,mod;
ll w[N],f[N][N],g[N][N],h[N],binom[N][N];
inline ll C(int n,int m){return m<0||n<m?0:binom[n][m];}
int ascend(int c,int n,int m,vector<int> P,vector<int> W)
{
    ::n = n;mod = m;
    for(int i = 1;i <= n;i++)a[i] = P[i-1],lst[i] = a[i]?a[i]:lst[i-1];
    for(int i = 1;i < n;i++)w[i] = W[i-1];
    for(int i = 0;i <= n;i++)for(int j = binom[i][0] = 1;j <= i;j++)
        binom[i][j] = (binom[i-1][j-1]+binom[i-1][j])%mod;
    memset(f,0,sizeof f);memset(g,0,sizeof g);
    f[0][0] = w[0] = w[n] = 1;
    for(int i = 0;i <= n;i++)
    {
        int x = 0;ll s = 1;
        for(int j = i+1;j <= n;j++)
        {
            ll now = s*w[j]%mod;
            if(a[j])
            {
                if(x)break;
                x = j;memset(h,0,sizeof h);
                for(int p = 0;p <= i;p++)for(int q = 0;q <= p;q++)
                    (h[p] += f[i][q]*C(a[j]-1-lst[i],p-q)) %= mod;
            }
            for(int k = 0;k <= i;k++)
                if(!x)
                {
                    if(!f[i][k]&&!g[i][k])continue;
                    ll s1 = C(j-k,i-k),s2 = C(lst[i]-k,j-i),tmp = s*(1-w[j])%mod;
                    (f[j][k] += f[i][k]*s1%mod*now) %= mod;
                    (g[j][k] += f[i][k]*s1%mod*tmp) %= mod;
                    (f[j][k+j-i] += (f[i][k]+g[i][k])*s2%mod*now) %= mod;
                }
                else if(h[k])(f[j][k+j-x+1] += h[k]*now%mod*C(a[x]-1-k,j-x)%mod*C(x-1-k,i-k)) %= mod;
            s = s*(1-w[j])%mod;
        }
    }
    return (f[n][lst[n]]+mod)%mod;
}

T2 题解

Sub1 是简单的,加入 \(1\sim W\) 后依次问 \((\{i-1\},\{i\})\) 即可。

Sub2 考虑用 \(\{1,2,3\}\),然后问 \((\{0,3\},\{1,2\})\),可以发现 \(-1,0,1\) 分别对应 \(3,2,1\)

Sub3 考虑用 \(2^0,\ldots,2^{29}\),先确定 \(x\) 的二进制最高位,即找到最大的 \(i\) 使得 \((\{0,\ldots,i-1\},\{i\})\) 结果为 \(-1\)。接下来就知道了 \(x\) 一定位于 \(i+1\),二分即可。注意找最大 \(i\) 要从大到小找,这样总次数一定是 \(30\)

Sub4 可以发现每个询问最多提供 \(3\) 种信息,而 \(3^7\) 刚好比 \(2000\) 多一点,所以考虑三分。考虑如何判断 \(x\) 是否在区间 \([l,r]\) 内,于是我们先放入 \(1\sim 2000\),那么询问 \(\{l-1,r\}\),如果和为 \(l+r-1\) 那么有 \(x<l\),如果和为 \(l+r\) 那么 \(l\le x\le r\),如果和为 \(l+r+1\) 那么 \(x<r\)

于是每次将当前区间等分为三份并询问中间区间的 \(\{l-1,r\}\)\(l+r\) 的关系即可。为了凑出 \(l+r\),可以放入 \(20\)\(2200\)\(200\)\(2001\)\(220\)\(2000\),右边用 \(2200\)\(2001\) 来凑出 \(l+r\),左边放入对应个数个 \(2000\) 即可。因为这些数一定比 \(x\) 大所以都在序列末尾,是确定下标的。次数为 \(7\)

#include <bits/stdc++.h>
// #include "cake.h"
// #include "grader.cpp"
using namespace std;

int compare_tastiness(vector<int> S1, vector<int> S2);
vector<int> get(int l,int r)
{vector<int> vc;for(int i = l;i <= r;i++)vc.push_back(i);return vc;}
vector<int> bake_cakes(int N,int W,int K)
{
    vector<int> vc;
    if(W == 100||W == 3)return get(1,W);
    if(W == 1e9){for(int i = 0;i < 30;i++)vc.push_back(1<<i);}
    if(W == 2000)
    {
        vc = get(1,W);
        for(int t = 300;t--;)vc.push_back(2000);
        for(int t = 200;t--;)vc.push_back(2001);
        for(int t = 20;t--;)vc.push_back(2200);
    }
    return vc;
}
int pd(int l,int r)
{
    vector<int> vc1,vc2;int s = l+r;
    vc1.push_back(l-1);vc1.push_back(r);
    for(int i = 1;i <= s/200;i++)vc2.push_back(2500+i);
    for(int i = 1;i <= s%200;i++)vc2.push_back(2300+i);
    for(int i = 1;i <= vc2.size();i++)vc1.push_back(2000+i);
    return compare_tastiness(vc1,vc2);
}
int find_tastiness(int m,int W,int K)
{
    if(W == 100)
    {
        for(int i = 1;i <= 100;i++)
            if(!compare_tastiness({i-1},{i}))return i;
        return 0;
    }
    if(W == 3)
    {
        int s = compare_tastiness({0,3},{1,2});
        return !~s?3:!s?2:1;
    }
    if(W == 1e9)
    {
        int s = 29;
        while(s&&~compare_tastiness(get(0,s-1),{s}))s--;
        int x = 1<<s;
        vector<int> vc;vc.push_back(s);
        for(int i = s-1;~i;i--)
        {
            vc.push_back(i);
            if(compare_tastiness(vc,{s+1}) <= 0)x |= 1<<i;
            else vc.pop_back();
        }
        return x;
    }
    if(W == 2000)
    {
        int l = 1,r = 2000;
        while(l < r)
        {
            int len = r-l+1,nl = l+len/3,nr = r-(len+2)/3;
            int s = pd(nl,nr);
            if(!~s)r = nl-1;
            else if(!s)l = nl,r = nr;
            else l = nr+1;
        }
        return l;
    }
    return 0;
}

T3 题解

首先一个贪心思路为每次走到最近的满足条件的点,因为如果多走了放到后面走一定更优,复杂度为 \(\mathcal{O}(qn\log n)\)。每个条件是一个邻域,可以发现如果一个区间的所有邻域有交,那么直接将这个区间缩为它们的交答案不变。将每条边中间添加一个虚点,这样邻域的交依然是个邻域。而如果相邻两个邻域不相交,那么到达第二个邻域的点就是固定的。

用线段树来维护,一个区间能表示为什么信息,并且如何合并。可以发现每个线段树节点只有两种情况,一种是一个邻域,表示要走到这个邻域中,第二种是 \((x,y,s)\),表示首先要从起点走到 \(x\),然后将起点换为 \(y\),总距离为 \(s\)。线段树叶子节点都是一个邻域,考虑如何合并:

  • 两个邻域合并,如果邻域有交那么直接取交,否则求出两个圆心的路径与两个邻域交分别为点 \(x,y\),那么可以看成先走到 \(x\),然后再走到 \(y\),于是合并为 \((x,y,dis(x,y))\)
  • 邻域加 \((x,y,s)\),如果 \(x\) 在领域中则可以忽略这个领域,否则找到圆心到 \(x\) 的路径与邻域的交 \(u\),那么可以看成先走到 \(u\) 再走到 \(x\) 最后到 \(y\),于是合并为 \((u,y,s+dis(u,x))\)
  • \((x,y,s)\) 加领域,同上。
  • \((x_1,y_1,s_1)\)\((x_2,y_2,s_2)\),合并为 \((x_1,y_2,s_1+s_2+dis(y_1,x_2))\)

每次询问直接合并线段树对应节点即可,可以用 \(\mathcal{O}(1)\) 求 LCA 和 \(k\) 级祖先,每次合并复杂度 \(\mathcal{O}(1)\),总复杂度为 \(\mathcal{O}(n\log n+q\log m)\),但实测不如树剖求 LCA 和 \(k\) 级祖先。

#include <bits/stdc++.h>
// #include "gems.h"
// #include "grader.cpp"
#define ls x<<1
#define rs x<<1|1
#define lson x<<1,l,mid
#define rson x<<1|1,mid+1,r
#define ll long long
using namespace std;
const int N = 6e5+5;
int a[N],b[N],n,m;
int fa[N],siz[N],son[N],hd[N],dfn[N],rk[N],top[N],d[N],cnt,tim;
struct edge{int to,nex;}e[N<<1];
inline void add(int u,int v){e[++cnt] = {v,hd[u]};hd[u] = cnt;}
inline void adde(int u,int v){add(u,v);add(v,u);}
void dfs1(int u,int dad)
{
    fa[u] = dad;d[u] = d[dad]+1;siz[u] = 1;
    for(int i = hd[u],v;i;i = e[i].nex)if((v = e[i].to) != dad)
    {
        dfs1(v,u);siz[u] += siz[v];
        if(siz[v] > siz[son[u]])son[u] = v;
    }
}
void dfs2(int u,int tp)
{
    top[u] = tp;rk[dfn[u] = ++tim] = u;
    if(son[u])dfs2(son[u],tp);
    for(int i = hd[u],v;i;i = e[i].nex)if((v = e[i].to) != fa[u]&&v != son[u])
        dfs2(v,v);
}
int lca(int u,int v)
{
    for(;top[u] != top[v];u = fa[top[u]])
        if(d[top[u]] < d[top[v]])swap(u,v);
    return d[u] < d[v]?u:v;
}
int dis(int u,int v){return d[u]+d[v]-2*d[lca(u,v)];}
int kfa(int u,int k)
{
    int s = d[u]-k;
    while(d[top[u]] > s)u = fa[top[u]];
    return rk[dfn[u]-(d[u]-s)];
}
int calc(int u,int v,int k)
{
    int x = lca(u,v),now = d[u]+d[v]-2*d[x];
    return d[x] <= d[u]-k?kfa(u,k):kfa(v,now-k);
}
struct node
{
    int tp,x,y;ll s;
    friend node operator + (const node &x,const node &y)
    {
        if(!x.tp&&!y.tp)
        {
            int d = dis(x.x,y.x);
            if(x.y+y.y < d)
            {
                int u = calc(x.x,y.x,x.y),v = calc(y.x,x.x,y.y);
                return {1,u,v,dis(u,v)};
            }
            if(x.y >= d+y.y)return y;
            if(y.y >= d+x.y)return x;
            int ny = x.y+y.y-d>>1;
            return {0,calc(x.x,y.x,x.y-ny),ny,0};
        }
        if(!x.tp&&y.tp)
        {
            if(dis(x.x,y.x) <= x.y)return y;
            int u = calc(x.x,y.x,x.y);
            return {1,u,y.y,dis(u,y.x)+y.s};
        }
        if(x.tp&&!y.tp)
        {
            if(dis(x.y,y.x) <= y.y)return x;
            int u = calc(y.x,x.y,y.y);
            return {1,x.x,u,dis(u,x.y)+x.s};
        }
        return {1,x.x,y.y,x.s+y.s+dis(x.y,y.x)};
    }
}t[N<<2];
void build(int x,int l,int r)
{
    if(l == r){t[x] = {0,a[l],b[l],0};return ;}
    int mid = l+r>>1;
    build(lson);build(rson);
    t[x] = t[ls]+t[rs];
}
node query(int x,int l,int r,int L,int R)
{
    if(L <= l&&r <= R)return t[x];
    int mid = l+r>>1;
    if(R <= mid)return query(lson,L,R);
    if(mid < L)return query(rson,L,R);
    return query(lson,L,R)+query(rson,L,R);
}
void gems(int c,int n,int m,vector<int> U,vector<int> V,vector<int> A,vector<int> D)
{
    ::n = n;::m = m;
    for(int i = 0;i < n-1;i++)adde(U[i],n+i+1),adde(n+i+1,V[i]);
    dfs1(1,0);dfs2(1,1);
    for(int i = 1;i <= m;i++)a[i] = A[i-1],b[i] = D[i-1]<<1;
    build(1,1,m);
}
ll query(int x,int l,int r)
{
    node res = node{0,x,0,0}+query(1,1,m,l,r);
    return res.s/2;
}

posted @ 2026-05-13 21:12  max0810  阅读(46)  评论(0)    收藏  举报