OIFC 2025.11.14 模拟赛总结

没挂分。

T1 蛇

\[\texttt{swap(T1, T2);} \]

题目描述

你获得了一个长度为 \(n\) 的字符串 \(a\),它的每一个位置都印有一个字符 o 或者 x。长期看着一个相同的字符串十分容易产生审美疲劳,所以你决定对它进行一些操作。

你选取了一个参数 \(k\)。则你可以任意次数的对字符串 \(a\) 进行如下两种操作之一(也可以不操作):

  1. 选择一个长度为 \(k\) 的区间 \([l,r]\)。将字符串中第 \(l\) 个到第 \(r\) 个位置(下标从 \(1\) 开始,下同)的字符取反。即 o 变成 xx 变成 o

  2. 选择一个长度为 \(k\) 的区间 \([l,r]\)。如果字符串中第 \(l\) 个到第 \(r\) 个位置的字符全部是一样的,就可以删除它们,然后剩余字符拼接起来。

为了完全理解第二种操作,这里有一个例子:

字符串 \(a\)oxxoooxox,并且选取了参数 \(k = 3\)。那么可以对区间 \([4,6]\) 进行操作二,因为这个区间的字符全部都是 o。操作完成后,\(a\) 将会变成 oxxxox,这是 oxxxox 拼接的结果。

非常不幸的是,字符串 \(a\) 被污染了。具体来说,某些位置变成了 ?,你不记得这个位置究竟是 o 还是 x 了,所以你认为都是有可能的。

你想要知道,有多少个字符串 \(b\) 可能被 \(a\) 通过上述两种操作表示出来?\(b\) 可以是空串。由于可能的 \(b\) 实在是太多了,所以你只需要输出答案对 \(10^9 + 7\) 取模后的结果。

数据范围:\(1 \leq T \leq 10\)\(1 \leq k \leq n \leq 10^5\)\(a_i \in \{o,x,?\}\)

题解

T2 白日窃贼

\[\texttt{swap(T2, T1);} \]

题目描述

你获得了一个长度为 \(n\) 的序列 \(a_1,a_2,\cdots,a_n\),这个序列保证了 \(1 \sim n\) 中的每一个数字都仅出现了恰好一次。

因为序列不是有序的,所以你决定将它升序排序。具体来说,每一次你可以交换两个数字。强大的你轻松观察到了使得序列升序排序的最优交换策略,所以为了不让你太骄傲,我需要对你稍加约束:在交换 \(a_x,a_y\) 的时候,需要满足 \(|a_x - a_y| = k\)

你敏锐的发现对于所有的 \(k\),并不是每一个都可以成功的使序列排序。所以你需要对于每一个 \(k(1 \leq k \leq n )\),判断是否有解。如果有解,输出使序列排序的最少交换次数。

数据范围:\(1 \leq n \leq 10^5\)

题解

注意到 \(k = 1\) 就相当于逆序对个数,容易证明。

打开大样例,发现有大量的 \(-1\),并且在 \(k=1,2,3,4,6,9,12,18,36\) 时有解。

于是可以直接猜出来当 \(k\)\(\gcd(|a_i - i|)\) 的因数时有解。

接下来,根据 \(k = 1\) 时时逆序对和小样例,容易发现是对于所有模 \(k\) 同余的 \(a_i\) 分别求逆序对再相加。

因此写一个树状数组求逆序对就行了。注意清空树状数组不要用 memset,怎加的怎么删就行。

时间复杂度为 \(\mathcal{O}(n \log n \times d(n))\),其中 \(d(n)\) 表示 \(n\) 以内的数最多有多少因数。

参考代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
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 << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * f;
}
inline void write(int x){
    if(x < 0) putchar('-'), x = -x;
    if(x > 9) write(x / 10);
    putchar(x % 10 + '0');
    return;
}
int t[100005];
inline int lowbit(int x){
    return x & (-x);
}
inline void modify(int x, int k){
    while(x <= 100001){
        t[x] += k;
        x += lowbit(x);
    }
    return;
}
inline int query(int x){
    int res = 0;
    while(x){
        res += t[x];
        x -= lowbit(x);
    }
    return res;
}
inline int getni(vector<int> v){
    int res = 0;
    for(auto p : v){
        res += query(100001) - query(p);
        modify(p, 1);
    }
    for(auto p : v) modify(p, -1);
    return res;
}
int n, a[100005];
vector<int> v[100005];
inline int solve(int k){
    for(int i = 1; i <= n; i++) v[a[i] % k].push_back(a[i]);
    int res = 0;
    for(int i = 0; i < k; i++){
        res += getni(v[i]);
        v[i].clear();
    }
    return res;
}
signed main(){
    freopen("snow.in", "r", stdin);
    freopen("snow.out", "w", stdout);
    n = read();
    for(int i = 1; i <= n; i++) a[i] = read();
    int ans = abs(a[1] - 1);
    for(int i = 2; i <= n; i++) ans = __gcd(abs(a[i] - i), ans);
    for(int i = 1; i <= n; i++){
        if(ans % i == 0){
            write(solve(i));
        }else{
            write(-1);
        }
        putchar('\n');
    }
    return 0;
}

T3 树形分流器

题目描述

咕咕咕。

题解

结论:不管 \(l,r\) 具体是多少,对于同一个点 \(v\)\(f(v,l,r)\) 取到最优值的策略都是相同的。

定义策略为将子树内所有非叶子结点的 \(a\) 调节成 \(l \sim r\) 的排列中,所有 \(a_i\) 在排列中的排名。

假设最优策略是 \(a_i \leftarrow l + p_i\),则 \(f\)\(\sum (a_i - l - p_i)^2\),拆开相减后发现大部分都抵消了,\(l\) 相关的可以直接计算,\(p_i\) 相关的只有 \(\sum p_i\),由于 \(p\) 是排列所以也可算。至于 \(a_i\),我们需要维护 \(u\) 子树内 \(a\) 的和。显然这可以用 dfn + BIT 解决,于是就做完了。

参考代码:

#include<bits/stdc++.h>
#define int __int128
using namespace std;

namespace IO{
    const int SIZE=1<<21;
    static char ibuf[SIZE],obuf[SIZE],*iS,*iT,*oS=obuf,*oT=oS+SIZE-1;
    int qr;
    char qu[55],c;
    bool f;
    #define getchar() (IO::iS==IO::iT?(IO::iT=(IO::iS=IO::ibuf)+fread(IO::ibuf,1,IO::SIZE,stdin),(IO::iS==IO::iT?EOF:*IO::iS++)):*IO::iS++)
    #define putchar(x) *IO::oS++=x,IO::oS==IO::oT?flush():0
    #define flush() fwrite(IO::obuf,1,IO::oS-IO::obuf,stdout),IO::oS=IO::obuf
    #define puts(x) IO::Puts(x)
    template<typename T>
    inline void read(T&x){
        for(f=1,c=getchar();c<48||c>57;c=getchar())f^=c=='-';
        for(x=0;c<=57&&c>=48;c=getchar()) x=(x<<1)+(x<<3)+(c&15); 
        x=f?x:-x;
    }
    template<typename T>
    inline void write(T x){
        if(!x) putchar(48); if(x<0) putchar('-'),x=-x;
        while(x) qu[++qr]=x%10^48,x/=10;
        while(qr) putchar(qu[qr--]);
    }
    inline void Puts(const char*s){
        for(int i=0;s[i];++i)
            putchar(s[i]);
        putchar('\n');
    }
    struct Flusher_{~Flusher_(){flush();}}io_flusher_;
}
using IO::read;
using IO::write;

int n, q, l[2000005], r[2000005], a[2000005], dfn[2000005], sz[2000005], timer = 0;

namespace BIT{
    int t[2000005];
    inline int lowbit(int x){
        return x & (-x);
    }
    inline void modify(int x, int k){
        while(x <= 2 * n - 1){
            t[x] += k;
            x += lowbit(x);
        }
        return;
    }
    inline int query(int x){
        int res = 0;
        while(x){
            res += t[x];
            x -= lowbit(x);
        }
        return res;
    }
}

inline void dfs(int u){
    if(!u) return;
    dfn[u] = ++timer;
    dfs(l[u]), dfs(r[u]);
    sz[u] = 1 + sz[l[u]] + sz[r[u]];
    return;
}

signed main(){
    freopen("tree.in", "r", stdin);
    freopen("tree.out", "w", stdout);
    read(n), read(q);
    for(int i = 1; i <= 2 * n - 1; i++) read(l[i]), read(r[i]);
    dfs(1);
    while(q--){
        int u, y, v, l1, r1, l2, r2;
        read(u), read(y), read(v), read(l1), read(r1), read(l2), read(r2);
        BIT::modify(dfn[u], -a[u]), a[u] = y, BIT::modify(dfn[u], a[u]);
        write((l1 * l1 - l2 * l2) * (r1 - l1) - 2 * (l1 - l2) * (BIT::query(dfn[v] + sz[v] - 1) - BIT::query(dfn[v] - 1)) + 2 * (l1 - l2) * (r1 - l1) * (r1 - l1 + 1) / 2);
        putchar('\n');
    }
    return 0;
}
posted @ 2025-11-14 13:48  zhang_kevin  阅读(13)  评论(0)    收藏  举报