Contestant~. Rank 316. Rating +66.

A. Dalton the Teacher

题意

给定一个序列,定义一次操作为任意交换两个数。

输出让所有 \(a_i \neq i\) 的最小操作数。

思路

显然,我们直接统计 \(a_i = i\) 的个数,这些数就是参与交换的。

我们推一下式子,最后答案就是 \(\frac{cnt + 1}{2}\)

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

对应AC代码

#include <bits/stdc++.h>

using namespace std;

//#define FLOATING_OCEAN

#define int long long
#define pii pair<int, int>
#define pipi pair<pii, pii>
#define ppii pair<int, pii>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#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;
    int cnt = 0;
    for(int i=1;i<=n;i++){
        int x;
        cin >> x;
        if(x == i) cnt ++;
    }
    cout << (cnt + 1) / 2 << '\n';
}

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

签到。这个式子已经有种经验感了(

B. Longest Divisors Interval

题意

给定一个整数 \(n\),输出最长的区间 \([l, r]\) 的长度,满足对于所有 \(l \leq i \leq r\),都有 \(n \bmod i = 0\)

思路

显然,如果我们找到了某个合法的 \([l, r]\),那么在 \([1, r - l + 1]\) 中,可以通过倍数关系知道所有数也都合法。

那么,我们直接令 \(l = 1\),暴力枚举右端点,当 \(x \bmod i \neq 0\) 时,合法区间就是 \([1, x - 1]\)

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

对应AC代码

#include <bits/stdc++.h>

using namespace std;

//#define FLOATING_OCEAN

#define int long long
#define pii pair<int, int>
#define pipi pair<pii, pii>
#define ppii pair<int, pii>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#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;
    int ans = 0;
    for(int i=1;;i++){
        if(n % i == 0) ans ++;
        else break;
    }
    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(nullptr), cout.tie(nullptr);
    int t = 1;
    cin >> t;
    while (t--) solve();
}

先乱猜再说

C1. Dual (Easy Version)

题意

给定一个长为 \(n\) 的序列 \(a\),满足 \(n \leq 20, |a_i| \leq 20\)

定义一次操作指定两个参数 \(i, j\),其中 \(i\)\(j\) 可以相等,将 \(a_j\) 加上 \(a_i\)

输出一种操作数不大于 \(50\) 的方案,使序列不递减。

思路

首先,我们思考一下,我们在构造什么的时候,能得到一个不递减序列。

不难发现,我们在求非负数序列的前缀和的时候,得到的前缀和数组就是不递减的。

同样,我们在求非正数序列的后缀和的时候,得到的后缀和数组也是不递减的。

那么,我们就可以将所有正数变成负数,或将所有负数变成正数,然后 从右向左 或 从左向右 依次加上即可。

为了让变号的操作数尽可能少,我们以将负数变成正数为例,我们自然希望用最大的正数来进行赋值。

但是可能会出现正数太小的情况。这时,因为 \(2^5 = 32 \geq 20\),所以我们最多只需 \(5\) 次操作即可让这个最大数 \(\geq 20\)

如上,我们可以在 \(5 + 2(n - 1) \leq 43\) 次操作内完成此题。

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

对应AC代码

#include <bits/stdc++.h>

using namespace std;

//#define FLOATING_OCEAN

#define int long long
#define pii pair<int, int>
#define pipi pair<pii, pii>
#define ppii pair<int, pii>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#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> a(n + 2);
    bool f = true;
    int mx = 1, mn = 1;
    for(int i=1;i<=n;i++){
        cin >> a[i];
        if(a[i] < a[i - 1]) f = false;
        if(a[i] > a[mx]) mx = i;
        if(a[i] < a[mn]) mn = i;
    }
    if(f){
        cout << 0 << '\n';
        return;
    }
    vector<pii> ans;
    if(a[mx] > 0) {
        while (a[mx] <= 20) {
            ans.pb(mx, mx);
            a[mx] *= 2;
        }
        for (int i = 1; i <= n; i++) {
            if (a[i] < 0) {
                ans.pb(i, mx);
                a[i] += a[mx];
            }
        }
        for (int i = 2; i <= n; i++) {
            if (a[i] < a[i - 1]) {
                ans.pb(i, i - 1);
                a[i] += a[i - 1];
            }
        }
    }else {
        while (a[mn] >= -20) {
            ans.pb(mn, mn);
            a[mn] *= 2;
        }
        for (int i = 1; i <= n; i++) {
            if (a[i] > 0) {
                ans.pb(i, mn);
                a[i] += a[mn];
            }
        }
        for (int i = n - 1; i >= 1; i--) {
            if (a[i] > a[i + 1]) {
                ans.pb(i, i + 1);
                a[i] += a[i + 1];
            }
        }
    }
    cout << ans.size() << '\n';
    for (auto [x, y]: ans) cout << x << ' ' << y << '\n';
}

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

主打一个灵感乍现,但是怎么吃了两发罚时啊啊啊啊啊

D2. Dual (Hard Version)

题意

\(D1\) 的基础上,限制操作数最多 \(31\) 次。

思路

我们先考虑优化将绝对值最大的数变为 \(\geq 20\) 的操作。

我们以将最大值变为 \(\geq 20\) 为例。因为显然,我们有时候不需要将其变为 \(\geq 20\),而是只需要将其变为大于最小值的绝对值。

那么,我们设此类操作需要执行 \(x1\) 次;同样的,在改变最小值的时候,此类操作需要执行 \(y1\) 次。

因为我们只需在 改变最大值 和 改变最小值 中选一个进行操作,因而 \(x1, y1\) 中,有一个操作数 因为不需要执行 而为 \(0\),另一个操作数 因为需要执行 而最多为 \(5\)

也就是说,\(x_1 + y_1 \leq 5\)

接下来,我们需要将这个正数最大值加到所有负数上去,设其需要 \(x2\) 次操作;同样的,设负数最小值加到所有正数上去的操作数为 \(y2\)

显然,\(x2 + y2 \leq n \leq 20\)

那么,我们可以得到 \(x1 + x2 + y1 + y2 \leq 25\),因而,我们取操作数最小的操作,可得 \(\min(x1 + x2, y1 + y2) \leq \lfloor \frac{25}{2} \rfloor = 12\)

最后,我们需要执行最初我们在 \(C1\) 所说的,类似于求前后缀和的操作,这个操作最多有 \(n - 1 \leq 19\) 次。

从而,按照这个写法,我们取两种操作方案的操作数最小值,可在 \(12 + 19 = 31\) 次内完成操作,从而通过本题。

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

对应AC代码

#include <bits/stdc++.h>

using namespace std;

//#define FLOATING_OCEAN

#define int long long
#define pii pair<int, int>
#define pipi pair<pii, pii>
#define ppii pair<int, pii>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#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> a1(n + 2), a2(n + 2);
    bool f = true;
    int mx = 1, mn = 1;
    for(int i=1;i<=n;i++){
        cin >> a1[i];
        a2[i] = a1[i];
        if(a1[i] < a1[i - 1]) f = false;
        if(a1[i] > a1[mx]) mx = i;
        if(a1[i] < a1[mn]) mn = i;
    }
    if(f){
        cout << 0 << '\n';
        return;
    }
    vector<pii> ans1, ans2;
    if(a1[mx] > 0) {
        while(a1[mx] < -a1[mn]){
            ans1.pb(mx, mx);
            a1[mx] *= 2;
        }
        for (int i = 1; i <= n; i++) {
            if (a1[i] < 0) {
                ans1.pb(i, mx);
                a1[i] += a1[mx];
            }
        }
        for (int i = 2; i <= n; i++) {
            if (a1[i] < a1[i - 1]) {
                ans1.pb(i, i - 1);
                a1[i] += a1[i - 1];
            }
        }
    }
    if(a2[mn] < 0){
        while(a2[mn] > -a2[mx]){
            ans2.pb(mn, mn);
            a2[mn] *= 2;
        }
        for (int i = 1; i <= n; i++) {
            if (a2[i] > 0) {
                ans2.pb(i, mn);
                a2[i] += a2[mn];
            }
        }
        for (int i = n - 1; i >= 1; i--) {
            if (a2[i] > a2[i + 1]) {
                ans2.pb(i, i + 1);
                a2[i] += a2[i + 1];
            }
        }
    }
    if(ans1.size() > ans2.size()) swap(ans1, ans2);
    if(ans1.size() == 0) ans1 = ans2;
    cout << ans1.size() << '\n';
    for (auto [x, y]: ans1) cout << x << ' ' << y << '\n';
}

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

结论是好猜的,证明是难写的,代码是乱交的

D. Earn or Unlock

题意

对于一个竖直放置的牌堆,从上到下给定每张牌的点数 \(a_i\)。每张牌有解锁和未解锁两个状态。

规定一次操作为选定任意一张解锁的牌 \(i\),并执行下面两种操作任选其一:

  1. 从上往下依次解锁未解锁的牌 \(a_i\) 张;

  2. 获得这个牌的点数 \(a_i\).

当没有已解锁的牌,或者没有牌剩余,游戏结束。

输出游戏结束时最多的点数。

思路

我们用 \(bitset\) 维护使用了前 \(i\) 张牌的某些牌后,能拓展到的右端点。

我们记使用的 \(bitset\)\(bs\)

那么,如果我们使用 \(a_i\)\(bs |= (bs << a_i)\);不使用,\(bs |= bs\)

为了方便不使用中间某张牌的推算,我们在使用第 \(i\) 个牌拓展右端点后,将 \(i\) 位置标记为不可达,这样即可将所有状态都判断全。

上述思路可以类比 \(01\) 背包。

最后,如果某个点可达,我们可以推得该点可以获得的最大点数是唯一确定的 \(sum[i] - i + 1\),其中 \(sum\) 是前缀和。

时间复杂度:\(O(\frac{n^2}{64})\)

对应AC代码

#pragma GCC optimize("O3,unroll-loops")
#pragma GCC target("avx2,bmi,bmi2,lzcnt,popcnt,fma")

#include <bits/stdc++.h>

using namespace std;

//#define FLOATING_OCEAN

#define int long long
#define pii pair<int, int>
#define pipi pair<pii, pii>
#define ppii pair<int, pii>
#define pci pair<char, int>
#define fs first
#define sc second
#define pb emplace_back
#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> a(n + 1);
    for(int i=1;i<=n;i++) cin >> a[i];
    bitset<N> bs;
    int sum = 0, ans = 0;
    bs[1] = 1;
    for(int i=1;i<=n;i++){
        sum += a[i];
        if(bs[i]) ans = max(ans, sum - i + 1);
        bs |= bs << a[i];
        bs[i] = 0;
    }
    for(int i=n+1;i<=2*n;i++){
        if(bs[i]) ans = max(ans, sum - i + 1);
    }
    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(nullptr), cout.tie(nullptr);
    int t = 1;
    //cin >> t;
    while (t--) solve();
}

听懂了吗,如懂。

其实这题 \(n^2\) 的解法挺多也挺好想,但放到 \(bitset\) 里还是太抽象了