2026.3~2026.6 文化课期间复建 OI 记录

前言:高一的 noip2025 倒闭了,然后原地退役。但是平常还是想稍微开开脑子愉悦一下身心,遂偶尔做题,记录于此。

由于题解是写给自己看的,所以不太建议阅读,如果真的想读,请配合 Gemini 食用

Luogu P15652. [省选联考 2026] 排列游戏 / perm *2000

注意到 \(\mathop{\text{mex}}\limits_{i\in[l,r]}\{a_i\}=\min\limits_{i\in[0,l)\cup(r,n)}\{a_i\}\),因此我们相当于只能确定一些前后缀区间并的 \(\min\),因此只要确定出这些那么就一定够我们构造一个合法的排列了。

由于我们只能问前后缀,所以不妨问出所有前、后缀 \(\min\)。由于问到 \(0\) 之后后面询问的结果都是 \(0\),因此可以问前缀问到 \(0\) 后停止,然后直接从后缀开始问到 \(0\) 分界后的位置。这样最坏的询问的次数就是 \(n\)

确定出前、后缀 \(\min\) 后,考虑填剩下的数。我们一定确定的数排列起来,大小关系形如 \(x_1>x_2>\cdots>x_p>0<y_q<\cdots<y_2<y_1\) 的谷形,因此数字越大、限制越松。因此我们可以考虑从 \(0\) 开始向两端从小到大填数,先填限制紧的,即最开始先填 \(\min(x_p,y_q)\) 对应的区间,然后向两端扩展,以此类推。

时间复杂度 \(O(n)\)

#include<bits/stdc++.h>
#include "perm.h"
#define N 30005
#define ll long long
#define mod 
using namespace std;

// void init(int,int);
// vector<int>perm(int);
int query(int,int);

int n,a[N],pre[N],nxt[N];
bool fl[N];
struct node{
    int l,r,d;
};
bool operator<(node x,node y){return max(a[x.l],a[x.r])>max(a[y.l],a[y.r]);}

void init(int c,int t){}

vector<int>perm(int n0)
{
    n=n0;
    fill(a,a+n,-1);
    fill(pre,pre+n,-1);
    fill(nxt,nxt+n,-1);
    fill(fl,fl+n,0);
    int p=0,lst=-1;
    while(p<n-1)
    {
        int x=query(p+1,n-1);
        if(!p||lst!=x) a[p]=x,fl[x]=1;
        if(!x) break;
        lst=x;
        p++;
    }
    int q=n-1;
    lst=-1;
    while(p<q)
    {
        int x=query(0,q-1);
        if(!x) break;
        if(!q||lst!=x) a[q]=x,fl[x]=1;
        lst=x;
        q--;
    }
    for(int i=0;i<n;i++)
    {
        if(a[i]) continue;
        goto lab;
    }
    a[n-1]=0;
    fl[0]=1;
    lab:;
    // for(int i=0;i<n;i++) cerr<<a[i]<<' ';cerr<<'\n';
    lst=0;
    for(int i=0;i<n;i++)
    {
        if(a[i]<0) continue;
        lst=i;
        break;
    }
    for(int i=1;i<n;i++)
    {
        if(a[i]<0) continue;
        pre[i]=lst;
        nxt[lst]=i;
        lst=i;
    }
    priority_queue<node>que;
    for(int i=0;i<n;i++)
    {
        if(a[i]) continue;
        que.push({pre[i],i,0});
        que.push({i,nxt[i],1});
    }
    int num=0;
    while(!que.empty())
    {
        auto [l,r,d]=que.top();
        que.pop();
        // cerr<<"["<<l<<','<<r<<"] "<<d<<'\n';
        for(int i=l+1;i<r;i++)
        {
            while(fl[num]) num++;
            a[i]=num;
            fl[num]=1;
        }
        if(!d&&l>0) que.push({pre[l],l,0});//,cerr<<"ins ["<<pre[l]<<','<<l<<"]\n";
        else if(d&&~r&&r<n-1) que.push({r,nxt[r],1});//,cerr<<"ins ["<<r<<','<<nxt[r]<<"]\n";
        // for(int i=0;i<n;i++) cerr<<a[i]<<' ';cerr<<'\n';
    }
    vector<int>res;
    for(int i=0;i<n;i++) res.push_back(a[i]);
    return res;
}

Luogu P10767. 「CROI · R2」在相思树下 II *2200

dp,设当前节点为 \(i\),左、右儿子为 \(x,y\)

那么设 \(l_i,r_i\) 分别表示能够在 \(i\) 号节点获胜的选手至少有多少个比这个节点获胜的值 \(d\) 小/大。

这句话有点绕,但是我不太会表述,洛谷题解也全都是一坨,建议配合 Gemini 食用

考虑这一轮为 \(\max\) 的情况。对于下界,由于都比 \(d\) 小,所以易得 \(l_i=l_x+l_y+1\);对于上界,只有胜出的是可以被计入的,最坏情况即 \(r_i=\min(r_x,r_y)+1\)\(\min\) 的情况同理。

于是某一层的范围就是这一层所有的节点上、下界的最小值。设第 \(i\) 层下、上界为 \(f_i,g_i\) 的话,那么询问即要求 \(a\in(f_b,2^n-g_b+1)\)

时间复杂度 \(O(2^n+m)\)

#include<bits/stdc++.h>
#define N 1000005
#define M 25
#define ll long long
#define lc (i<<1)
#define rc (i<<1|1)
#define mid (l+r>>1)
#define mod 
using namespace std;

int n,m,a[N<<1],fl[N<<1],fr[N<<1],f[M],g[M];

void dp(int i,int l,int r)
{
    if(l>=r) return;
    int j=log2(r-l+1);
    dp(lc,l,mid);
    dp(rc,mid+1,r);
    if(a[i]==1)
    {
        fl[i]=fl[lc]+fl[rc]+1;
        fr[i]=min(fr[lc],fr[rc]);
    }
    else
    {
        fl[i]=min(fl[lc],fl[rc]);
        fr[i]=fr[lc]+fr[rc]+1;
    }
    f[j]=min(f[j],fl[i]);
    g[j]=min(g[j],fr[i]);
}

int main()
{
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<(1<<n);i++) cin>>a[i];
    fill(f+1,f+n+1,1e9);
    fill(g+1,g+n+1,1e9);
    dp(1,1,1<<n);
    while(m--)
    {
        int x,y;
        cin>>x>>y;
        y--;
        cout<<(f[y]<x&&g[y]+x-1<(1<<n)?"Yes":"No")<<'\n';
    }
    return 0;
}

Luogu P12336. 第三心脏 *2800

首先题目的式子等价于:

\[a^2+b^2+c^2+d^2=(a\oplus b\oplus c\oplus d)^2 \]

那么有 \(a\oplus b\oplus c\oplus d<d\)

考虑令 \(a\oplus b\oplus c\oplus d=d+1\),则有:

\[\begin{aligned} a^2+b^2+c^2+d^2&=(d+1)^2\\ a^2+b^2+c^2&=2d+1\\ d&=\dfrac{a^2+b^2+c^2-1}{2} \end{aligned} \]

这个 \(d\) 一定大于 \(a\),因此不论我们怎么构造 \(b,c\) 都可以只需将 \(b,c,d\) 升序重排得到最终的构造。

与此同时,如果要 \(a\oplus b\oplus c\oplus d=d+1\) 成立,那么显然一定得有 \(a\oplus b\oplus c=1\)\(2\mid d\)\(2\mid d\) 等价于 \(2\nmid(a^2+b^2+c^2)\)

因此现在的问题就变成了:给定 \(a\),构造 \(b,c\) 使得 \(a\oplus b\oplus c=1\)\(a<b<c\)\(2\nmid(a^2+b^2+c^2)\)

为了让 \(b\oplus c=a\oplus 1\) 的同时 \(b,c>a\),如果我们令 \(k=\min\{k\mid 2^k>a\}\),就可以:

  • \(2\mid a\) 时,令 \(b=2^k+a\)\(c=b\oplus a\oplus 1\)
  • \(2\nmid a\) 时,令 \(b=2^k\)\(c=b+(a\oplus 1)\)

容易验证它们都是对的。然后就可以求得 \(d\)

然后发现 \(a=1\) 时上述构造不合法。它可以通过暴力枚举找到一组解。

于是就做完了。

#include<bits/stdc++.h>
#define N 
#define ll long long
#define i128 __int128
#define mod 
using namespace std;

int main()
{
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    ll a;
    cin>>a;
    if(a==1)
    {
        cout<<"4 28 40";
        return 0;
    }
    int k=0;
    while((1ll<<k)<=a) k++;
    ll b,c;
    if(a&1) b=1ll<<k,c=b+(a^1);
    else b=a+(1ll<<k),c=b^a^1;
    if(b>c) swap(b,c);
    ll d=((i128)a*a+b*b+c*c-1)/2;
    cout<<b<<' '<<c<<' '<<d;
    //cerr<<'\n'<<((i128)a*a+(i128)b*b+(i128)c*c+(i128)d*d==(i128)(a^b^c^d)*(a^b^c^d));
    return 0;
}

Luogu P16258. [DSTOI Round 0] 万分之一的光 *2200

首先忽略 \(a\) 开头的 \(0+n\)。设 \(f_n\) 表示过程 \(n\) 对应的答案。易得转移式子:

\[f(n)=(2n-1)\oplus f(n-1)\oplus(n-1)\oplus f(n-2) \]

其中 \(f_1=0,f_2=3\)。然后尝试进行一些推导:

\[\begin{aligned} f(n)&=(2n-1)\oplus f(n-1)\oplus(n-1)\oplus f(n-2)\\ f(n-1)&=(2n-3)\oplus f(n-2)\oplus(n-2)\oplus f(n-3)\\ f(n)\oplus f(n-1)&=f(n-1)\oplus f(n-3)\oplus(n-1)\oplus(n-2)\oplus(2n-1)\oplus(2n-3)\\ f(n)&=f(n-3)\oplus(n-1)\oplus(n-2)\oplus(2n-1)\oplus(2n-3)\\ &=f(n-3)\oplus(n-1)\oplus(n-2)\oplus 2(n-1) \oplus 2(n-2)\\ \end{aligned} \]

也就是说,如果令 \(g(n)=n\oplus (n-3)\oplus (n-6)\oplus\cdots\),则 \(f_n=g(n-1)\oplus g(n-2)\oplus 2g(n-1)\oplus 2g(n-2)\)。现在需要计算 \(g(n)\)

公差是 \(3\) 在二进制上感觉是没有什么比较好的规律的,那么考虑拆位。设 \(f_{i,j}=\bigoplus\limits_{0\le x<2^i\land x\bmod 3=j}x\),转移从 \(i-1\) 过来,即有:

\[f_{i,j}=f_{i-1,j}\oplus f_{i-1,(j-2^{i-1})\bmod 3}\oplus [d]\cdot 2^{i-1} \]

其中 \([d]\) 表示对应的区间内长度是否为奇数,显然只有奇数的长度时高位才会有新的贡献。

然后可以利用 \(f\) 逐位进行计算即可。

最终答案也可以求出来,有一些比较特殊的情况需要小心,详见代码。

#include<bits/stdc++.h>
#define V 60
#define M 64
#define ll long long
#define mod 
using namespace std;

ll f[M][3];

void init()
{
    for(int i=1;i<=V;i++)
    {
        for(int j=0;j<3;j++)
        {
            f[i][j]=f[i-1][j]^f[i-1][(j-(1ll<<i-1)%3+3)%3];
            if((((1ll<<i)+2-j)/3-((1ll<<i-1)+2-j)/3)&1) f[i][j]^=1ll<<i-1;
            // cerr<<"f["<<i<<"]["<<j<<"] = "<<f[i][j]<<'\n';
        }
    }
}

ll cal(ll n)
{
    ll res=n,j=n%3,s=0;
    for(int i=V;~i;i--)
    {
        if(n>>i&1^1) continue;
        res^=f[i][j];
        if(((1ll<<i)+2-j)/3&1) res^=s;
        j=(j-(1ll<<i)%3+3)%3;
        s+=1ll<<i;
        // cerr<<i<<' '<<j<<' '<<res<<'\n';
    }
    return res;
}

void sol()
{
    ll n;
    cin>>n;
    if(n<3)
    {
        cout<<"1\n";
        return;
    }
    if(n==3)
    {
        cout<<"7\n";
        return;
    }
    ll x=cal(n-1),y=cal(n-2);
    cout<<(n^x^(x<<1)^y^(y<<1)^(n%3==0))<<'\n';
}

int main()
{
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin>>t;
    init();
    while(t--) sol();
    return 0;
}

Luogu P16952. 「LAOI-18」口約束

果然退役的脑子只够给构造题了。

首先显然有一个必要条件是 \(n\equiv 2\pmod 3\)\(n\ge 8\)。猜测它是充分的并尝试构造。

考虑 \(\text{mex}=0\) 的部分。这一部分只能是全 \(1\),为了让 \(1\) 尽可能少,我们让其挨在一起。那么剩下的就不会再出现连续三个 \(1\) 了。

然后是 \(\text{mex}=1\) 的部分。这一部分只能是全 \(0\),我们不妨直接放在全 \(1\) 的一侧。

那么另一侧只能是 \(\text{mex}=2\)。现在可以确定的前缀是 \(00\cdots0011\cdots110\),前面已经出现三个 \(\text{mex}=2\) 的区间。后面接续下去的话,两个 \(0\)、一个 \(1\) 自然是比两个 \(1\)、一个 \(0\) 更优的,于是可以构造答案串:\(00\cdots0011\cdots11001001001\cdots\),于是就做完了。

#include<bits/stdc++.h>
#define N 
#define ll long long
#define mod 
using namespace std;

void sol()
{
    int n;
    cin>>n;
    if(n%3!=2||n<8)
    {
        cout<<"-1\n";
        return;
    }
    int k=(n-2)/3+2;
    for(int i=1;i<=k;i++) cout<<"0 ";
    for(int i=1;i<=k;i++) cout<<"1 ";
    for(int i=k*2+1,j=0;i<=n;i++,(++j)%=3) cout<<(j==2)<<' ';
    cout<<'\n';
}

int main()
{
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin>>t;
    while(t--) sol();
    return 0;
}
posted @ 2026-03-16 20:27  Jorisy  阅读(20)  评论(0)    收藏  举报