Contestant~. Rank 1201. Rating +25.

A. Increasing and Decreasing

题意

给定三个整数 \(x, y, n\),构造一个长为 \(n\) 的序列 \(a\),满足:

  1. \(a_1 = x, a_n = y\)

  2. \(a_1 < a_2 < \ldots < a_n\)

  3. \(b_i = a_{i + 1} - a_i\)\(b_1 > b_2 > \ldots > b_{n - 1}\)

如果无法构造,输出 \(-1\)

思路

既然要尽可能构造出来,那么我们就尽可能缩小相邻 \(b_i\) 的差值,也就是说,\(b_{n - 1} = 1, b_{n - 2} = 2, \ldots\)

我们令 \(a_n = y\),然后递推到 \(a_1\),最后比较一下 \(a_1\)\(x\) 的大小。

显然,如果 \(a_1 > x\),那么无解,否则就可以满足 \(a_2 - x \geq a_2 - a_1 = b_1 > b2 > \ldots\)

最后记得设 \(a_1 = x\)

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

对应AC代码

#define chatgpt "bits/stdc++.h"

#include chatgpt

using namespace std;

//#define FLOATING_OCEAN

#define int long long
#define pii pair<int, int>
#define pipi pair<pii, pii>
#define tpi tuple<int, int, int>
#define fs first
#define sc second
#define pb emplace_back
#define ep emplace
#define rall(x) x.rbegin(),x.rend()
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, M = 2e5 + 10, mod = 998244353, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int x, y, n;
    cin >> x >> y >> n;
    vector<int> ans(n + 1);
    ans[n] = y;
    for(int i=n-1;i>=1;i--){
        ans[i] = ans[i + 1] - (n - i);
    }
    if(ans[1] < x) cout << -1 << '\n';
    else{
        cout << x << ' ';
        for(int i=2;i<=n;i++) cout << ans[i] << ' ';
        cout << '\n';
    }
}

signed main() {
# ifdef FLOATING_OCEAN
    freopen("1.in","r",stdin);
    freopen("1.out","w",stdout);
# endif
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
//    init();
    int t = 1;
    cin >> t;
    while (t--) solve();
}

在多数签到题里有点偏模拟

B. Swap and Reverse

题意

给定一个字符串 \(s\) 以及一个整数 \(k\),定义一次操作为下面两者任选一:

  1. 选择一个点 \(i, i \in [1, n - 2]\),交换 \(s_i, s_{i + 2}\)

  2. 选择一个点 \(i, i \in [1, n - k + 1]\),将 \([i, i + k - 1]\) 内的字符串翻转

在任意次操作后,输出字符串的最小字典序。

思路

首先,如果没有操作 \(2\),那么最后的答案就是分别将奇数位和偶数位上的字符排序。

如果有操作 \(2\),我们先来考虑 \(k\) 是奇数的情况。

因为我们只能翻转奇数长度的字符,可以发现翻转操作可以等效于若干个操作 \(1\)

而相反地,如果 \(k\) 是偶数,我们可以将奇数位和偶数位上的数交换,因此在若干次操作后,我们可以将其等效于交换任意两个相邻数。

因而,我们就只需对整个字符串排序。

从而,最后的结论是:

  1. \(k\) 为奇数,分别对奇数位和偶数位上的字符排序;

  2. \(k\) 为偶数,将整个字符串排序

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

对应AC代码

#define chatgpt "bits/stdc++.h"

#include chatgpt

using namespace std;

//#define FLOATING_OCEAN

#define int long long
#define pii pair<int, int>
#define pipi pair<pii, pii>
#define tpi tuple<int, int, int>
#define fs first
#define sc second
#define pb emplace_back
#define ep emplace
#define rall(x) x.rbegin(),x.rend()
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, M = 2e5 + 10, mod = 998244353, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n, k;
    string s;
    cin >> n >> k >> s;
    if (k % 2 == 0) {
        sort(all(s));
        cout << s << '\n';
        return;
    }
    string a, b;
    for (int i = 0; i < n; i++) {
        if (i % 2 == 0) a += s[i];
        else b += s[i];
    }
    sort(all(a));
    sort(all(b));
    for (int i = 0; i < n; i++) {
        if (i % 2 == 0) cout << a[i / 2];
        else cout << b[i / 2];
    }
    cout << '\n';
}

signed main() {
# ifdef FLOATING_OCEAN
    freopen("1.in","r",stdin);
    freopen("1.out","w",stdout);
# endif
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
//    init();
    int t = 1;
    cin >> t;
    while (t--) solve();
}

有意思的思维题

C. Divisor Chain

题意

给定整数 \(x\),定义一次操作为选定 \(x\) 的一个因数 \(y\),并将 \(x\) 减去 \(y\)

在不超过 \(1000\) 次操作,且 不 连续 选择 两次以上 相同的因数的条件下,将 \(x\) 减为 \(1\),并输出一种可行的方案。

思路

我们将其放到二进制下考虑:

  1. 从第 \(2\) 位开始向高位枚举;

  2. 如果第 \(i\) 位为 \(1\),那么删去该位,也就是 \(1 << i\)

上述操作存在缺陷,因为如果出现最低位为 \(0\) 的情况,最后答案会变成 \(0\)

因此,我们考虑直接从最低位开始枚举删除,直到最高位的前一位。

当我们达到最高位时,可以发现后面低位全都是 \(0\),也就是说,现在 \(x\)\(2\) 的次方。那么我们只需循环减去 \(\frac{x}{2}\) 即可,最后一定会变为 \(1\)

时间复杂度:\(O(\log A)\)

对应AC代码

#define chatgpt "bits/stdc++.h"

#include chatgpt

using namespace std;

//#define FLOATING_OCEAN

#define int long long
#define pii pair<int, int>
#define pipi pair<pii, pii>
#define tpi tuple<int, int, int>
#define fs first
#define sc second
#define pb emplace_back
#define ep emplace
#define rall(x) x.rbegin(),x.rend()
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, M = 2e5 + 10, mod = 998244353, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n;
    cin >> n;
    vector<int> ans;
    ans.pb(n);
    bitset<64> a(n);
    for(int i=0;i<64;i++){
        if(a[i]){
            if(n == (1ll << i)){
                while(n > 1) ans.pb(n /= 2);
            }else ans.pb(n -= (1ll << i));
        }
    }
    cout << ans.size() << '\n';
    for(auto e : ans) cout << e << ' ';
    cout << '\n';
}

signed main() {
# ifdef FLOATING_OCEAN
    freopen("1.in","r",stdin);
    freopen("1.out","w",stdout);
# endif
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
//    init();
    int t = 1;
    cin >> t;
    while (t--) solve();
}

\(1000\) 纯属骗人

D. Matrix Cascade

题意

给定一个 \(n \times n\) 的二进制矩阵,定义一次操作为选定一个点 \((x, y)\),并将满足 \(x - i \ge \left|y - j\right|\) 的所有点 \((i, j)\) 上面的值全都反转。

输出最小操作数,使整个矩阵全变为 \(0\)

思路

我们不妨观察这样一个三角形:

 a
bcd

我们可以发现,点 \(c\) 只会被点 \(a, b, d\) 影响到,而整个三角形都是 \(a\) 的影响范围。

因而,我们考虑 \(dp[i][j][k]\),表示 \((i, j)\) 左边的点的状态,右边的点的状态 以及自身的状态。

因为相邻两行的 \(dp\) 具有递推性,所以我们考虑从上一行推到当前行。

那么很显然,左边的点只需考虑向右影响的递推,\(dp[i][j][0] = dp[i - 1][j - 1][0]\),同理,\(dp[i][j][1] = dp[i - 1][j + 1][1]\)

因而,\(dp[i][j][2] = dp[i][j][0] + dp[i][j][1] + dp[i - 1][j][2]\)

如果我们发现 \((i, j)\) 点需要修改,那么我们将 \(dp[i][j][0], dp[i][j][1], dp[i][j][2]\) 都加上 \(1\),来表征当前点可以影响下一行。

结合图形理解,可以证明上面的递推的合理性。

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

对应AC代码

#define chatgpt "bits/stdc++.h"

#include chatgpt

using namespace std;

//#define FLOATING_OCEAN

#define int long long
#define pii pair<int, int>
#define pipi pair<pii, pii>
#define tpi tuple<int, int, int>
#define fs first
#define sc second
#define pb emplace_back
#define ep emplace
#define rall(x) x.rbegin(),x.rend()
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, M = 2e5 + 10, mod = 998244353, inf = 0x3f3f3f3f3f3f3f3f;

void solve() {
    int n;
    cin >> n;
    vector<string> s(n);
    for(int i=0;i<n;i++) cin >> s[i];
    vector<vector<vector<int>>> dp(n + 2, vector<vector<int>>(n + 2, vector<int>(3)));
    int ans = 0;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            int now = s[i - 1][j - 1] - '0';
            dp[i][j][0] = dp[i - 1][j - 1][0];
            dp[i][j][1] = dp[i - 1][j + 1][1];
            dp[i][j][2] = dp[i][j][0] + dp[i][j][1] + dp[i - 1][j][2];
            if((dp[i][j][2] + now) % 2 == 1){
                ans ++;
                dp[i][j][0] ++, dp[i][j][1] ++, dp[i][j][2] ++;
            }
        }
    }
    cout << ans << '\n';
}

signed main() {
# ifdef FLOATING_OCEAN
    freopen("1.in","r",stdin);
    freopen("1.out","w",stdout);
# endif
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
//    init();
    int t = 1;
    cin >> t;
    while (t--) solve();
}

赛时还想出来个二分的写法,感觉有点抽象,而且常数不小

E. Guess Game

题意

给定序列 \(s\),定义 \(A, B\) 之间的游戏如下:

  1. 均匀随机选择两个数,可以重复,记为 \(a, b\)

  2. \(a, a|b\) 告诉 \(A\),若 \(A\) 能根据 之前自己和 对手 的猜测 来确定 \(a < b, a > b\)\(a = b\),那么说出结果并结束游戏,否则说出自己不知道;

  3. \(b, a|b\) 告诉 \(B\),若 \(B\) 能根据 之前自己和 对手 的猜测 来确定 \(a < b, a > b\)\(a = b\),那么说出结果并结束游戏,否则说出自己不知道;

  4. 不断猜测,直到有人说出结果为止

两个人足够聪明。

定义一个人为一轮,输出游戏进行的轮数的期望。

思路

既然两个人足够聪明,那么他们肯定会从高位向低位考虑。

我们来模拟一下过程,

其中,下面的过程会默认将 \(a|b\) "离散化" 为 低位全是 \(1\),或者说,将所有 \(1\) 挪到最前面:

  1. \(A\) 拿到了 \(a, a|b\),如果 \(a\) 的最高位为 \(0\),那么直接说出 \(a < b\),否咋说出自己不知道;

  2. \(B\) 拿到了 \(b, a|b\),如果 \(b\) 的最高位或者次高位 至少有一个 为 \(0\),那么因为 \(A\) 说自己不知道,就可以断定最高位一定是 \(1\),那么直接说出 \(a > b\),否则说出自己不知道;

  3. 如果 \(a\) 的次高位或者 次 次高位 至少有一个 为 \(0\),那么因为 \(B\) 说自己不知道,就可以断定 最高位 和 次高位 一定是 \(1\),那么直接说出 \(a < b\),否则说出自己不知道;

  4. \(\ldots\)

因而,最后我们可以推导出结论:

  1. 从高位向低位枚举所有 \(a|b\)\(1\) 的二进制位;

  2. 如果 \(a < b\),并且 \(a\) 的第 \(i\) 位是 \(0\),那么进行了 \(i + (i \bmod 2 == 0)\) 轮;

  3. 如果 \(a = b\),那么进行了 \(k + 1\) 轮,其中 \(k\)\(a | b\) 中有多少个 \(1\)

  4. 如果 \(a > b\),并且 \(b\) 的第 \(i\) 位是 \(0\),那么进行了 \(i + (i \bmod 2 == 1)\)

如上,我们得到了复杂度为 \(O(n ^ 2 \log A)\) 的暴力算法。

我们考虑使用 \(\mathtt{01}\) 字典树来优化复杂度。

具体来说,在 \(\mathtt{dfs}\) 的时候,我们可以记录当前路径上有多少个 \(1\),并可以轻松获取当前点是否同时具有 \(0, 1\) 节点,或同时不具有 \(0, 1\) 节点。

前者可以构造出大小关系,后者可以构造出相等关系。

将公式略微修改即可得到答案。

当然,不要忘记除 \(n^2\),也就是总方案数。

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

对应AC代码

#define chatgpt "bits/stdc++.h"

#include chatgpt

using namespace std;

//#define FLOATING_OCEAN

#define int long long
#define pii pair<int, int>
#define pipi pair<pii, pii>
#define tpi tuple<int, int, int>
#define fs first
#define sc second
#define pb emplace_back
#define ep emplace
#define rall(x) x.rbegin(),x.rend()
#define all(x) x.begin(),x.end()

const int N = 2e5 + 10, M = 2e5 + 10, mod = 998244353, inf = 0x3f3f3f3f3f3f3f3f;

struct Node {
    int nxt[2], cnt;
    Node() {
        nxt[0] = nxt[1] = -1;
        cnt = 0;
    }
};

vector<Node> tr;

void insert(int x){
    int now = 0;
    for(int i= 31; i >= 0;i--) {
        int p = (x >> i) & 1;
        if(tr[now].nxt[p] == -1) {
            tr[now].nxt[p] = tr.size();
            tr.pb();
        }
        tr[now].cnt ++, now = tr[now].nxt[p];
    }
    tr[now].cnt ++;
}

void query(int v, int k, int &ans){
    if(v == -1) return;
    //存在某一位上一个为0一个为1的情况
    if(tr[v].nxt[0] != -1 && tr[v].nxt[1] != -1){
        int cnt = tr[tr[v].nxt[0]].cnt * tr[tr[v].nxt[1]].cnt % mod;
        ans = (ans + cnt * (2 * ((k + 1) / 2) + 1) % mod) % mod; //a < b
        ans = (ans + cnt * 2 % mod * ((k + 2) / 2) % mod) % mod; //a > b
    }
    //一模一样的情况
    if(tr[v].nxt[0] == -1 && tr[v].nxt[1] == -1){
        ans = (ans + (k + 1) * tr[v].cnt % mod * tr[v].cnt % mod) % mod;
    }
    query(tr[v].nxt[0], k, ans);
    query(tr[v].nxt[1], k + 1, ans);
}

static __int128 qp(__int128 x, __int128 y, __int128 m = mod) {
    static __int128 ans;
    ans = 1, x %= m;
    for (; y; y >>= 1, x = x * x % m) if (y & 1) ans = ans * x % m;
    return ans;
}

int inv(int n, int m = mod) { return qp(n, m - 2, m); }

void solve() {
    int n;
    cin >> n;
    tr = {};
    tr.pb();
    for(int i=0;i<n;i++){
        int x;
        cin >> x;
        insert(x);
    }
    int ans = 0;
    query(0, 0, ans);
    ans = ans * inv(n * n) % mod;
    cout << ans << '\n';
}

signed main() {
# ifdef FLOATING_OCEAN
    freopen("1.in","r",stdin);
    freopen("1.out","w",stdout);
# endif
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
//    init();
    int t = 1;
    cin >> t;
    while (t--) solve();
}

不要尝试用 \(memset\) 初始化字典树 qaq