ARC060 题解

T1 Tak and Cards

考虑 \(dp[i][j]\) 表示用了前 \(i\) 个数,达到 \(j\) 这个和的方案数。暴力转移即可。

#include <iostream>
#define int long long
using namespace std;
int dp[55][2505];
int a[55];
signed main() {
    int n, avr;
    cin >> n >> avr;
    dp[0][0] = 1;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        for (int j = i; j; j--) {
            for (int k = 2500; k >= a[i]; --k) dp[j][k] += dp[j - 1][k - a[i]];
        }
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) ans += dp[i][min(i * avr, 2501ll)];
    cout << ans << "\n";
    return 0;
}

T2 Digit Sum

考虑 \(b\) 进制下 \(a\) 的各位数字和,实际上是类似于 \(a \bmod b + \lfloor \frac{a}{b} \rfloor \bmod b + \lfloor \frac{\lfloor \frac{a}{b} \rfloor}{b} \rfloor \bmod b + \cdots\) 这样的。根据下取整的性质,刚才那个式子可以被写成这样: \(\sum_{k = 0}^{b^k \le a} \lfloor \frac{a}{b^k} \rfloor \bmod b\)。我们发现当 \(b \le \sqrt{a}\) 时,暴力算这个式子复杂度是 \(\log\) 级别的。当 \(b > \sqrt{a}\) 时,这式子至多只用算 \(2\) 步,也就是 \(a \bmod b + \lfloor \frac{a}{b} \rfloor\)。观察到出现了下取整,于是我们考虑进行一个整除分块。设 \(val = \lfloor \frac{a}{b} \rfloor\),我们将所有 \(b > \sqrt{a}\) 按照 \(val\) 的值分类,只会有 \(\mathcal{O}(\sqrt{a})\) 类。根据题目要求,我们有 \(a \bmod b + val = s\),所以 \(a - b·val + val = s\),可以解得 \(b = \frac{n - s + val}{val}\)。若这个 \(b\) 在当前分类的范围内,那就说明我们找到了一个解。于是我们先暴力算 \(\le \sqrt{a}\) 的所有 \(b\),然后对剩下的 \(b\) 进行整除分块。最后要是 \(a = s\),那就有 \(b = a + 1\) 的解。总复杂度 \(\mathcal{O}(\sqrt{n}\log n)\),巨大跑不满。

#include <iostream>
#define int long long
using namespace std;
inline int calc(int n, int b) {
    int ret = 0;
    while (n) {
        ret += n % b;
        n /= b;
    }
    return ret;
}
signed main() {
    int n, s;
    cin >> n >> s;
    int x = 2;
    for (; x * x <= n; x++) {
        if (calc(n, x) == s) {
            cout << x << "\n";
            return 0;
        }
    }
    for (int l = x, r; l <= n; l = r + 1) {
        r = n / (n / l);
        int val = n / l;
        if ((n - s + val) % val != 0 || (n - s + val <= 0))
            continue;
        int b = (n - s + val) / val;
        if (l <= b && b <= r) {
            cout << b << "\n";
            return 0;
        }
    }
    cout << (n == s ? n + 1 : -1) << "\n";
    return 0;
}

T3 Tak and Hotels

直接倍增即可。从前往后搞一遍,从后往前搞一遍,询问时类似于二分地做。

#include <iostream>
#define int long long
using namespace std;
int to[100005][25];
int ot[100005][25];
int x[100005];
signed main() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> x[i];
    int dis, q;
    cin >> dis >> q;
    for (int i = 1; i <= n; i++) {
        int y = x[i] + dis;
        int l = i, r = n, mid, ans = n;
        while (l <= r) {
            mid = (l + r) >> 1;
            if (x[mid] <= y) 
                ans = mid, l = mid + 1;
            else 
                r = mid - 1;
        }
        to[i][0] = ans;
        y = x[i] - dis;
        l = 1, r = i, ans = 1;
        while (l <= r) {
            mid = (l + r) >> 1;
            if (x[mid] >= y) 
                ans = mid, r = mid - 1;
            else 
                l = mid + 1;
        }
        ot[i][0] = ans;
    }
    for (int i = 1; i <= 24; ++i) {
        for (int j = 1; j <= n; j++) {
            to[j][i] = to[to[j][i - 1]][i - 1];
            ot[j][i] = ot[ot[j][i - 1]][i - 1];
        }
    }
    while (q--) {
        int l, r;
        cin >> l >> r;
        int ans = 0;
        if (l < r) {
            int cur = l;
            for (int i = 24; ~i; --i) {
                if (to[cur][i] < r) 
                    ans |= (1 << i), cur = to[cur][i];
            }
        } else {
            swap(l, r);
            int cur = r;
            for (int i = 24; ~i; --i) {
                if (ot[cur][i] > l) 
                    ans |= (1 << i), cur = ot[cur][i];
            }
        }
        cout << ans + 1 << "\n";
    }
    return 0;
}

T4 Best Representation

首先观察到若 \(w\) 不是循环串,那答案必然是两个 \(1\)

其次我们考虑若 \(w\) 的循环节不是 \(1\),那我们把 \(w\) 的最后一个字符单拎出来,容易知道这样得到的两个字符串都不是循环串。那接下来只需要求方案数。由于只分了两端,所以我们考虑枚举分界点,然后判断前后是不是循环串。可以使用两次 \(KMP\),分别从前往后和从后往前求出两个 \(nxt\) 数组。然后我们会发现对于一个前缀 \(i\),它是循环串的充要条件是 \(nxt[i] \times 2 \ge i \land i - nxt[i] | i\)。对于后缀也是同理。于是我们可以在 \(\mathcal{O}(n)\) 的时间内完成这一部分。

最后如果 \(w\) 的循环节是 \(1\),那必然要在每两个字符间都划断,这样才能保证不分出循环串。于是这道题就做完了。

#include <iostream>
#include <algorithm>
using namespace std;
int nxt[500005][2];
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    string a;
    cin >> a;
    int n = a.size();
    a = ' ' + a;
    nxt[0][0] = -1;
    nxt[1][0] = 0;
    for (int i = 2, j = 1; i <= n; i++, j++) {
        while (j && a[i] != a[j]) j = nxt[j - 1][0] + 1;
        nxt[i][0] = j;
    }
    for (int i = 1; i <= n / 2; i++) swap(a[i], a[n - i + 1]);
    nxt[0][1] = -1;
    nxt[1][1] = 0;
    for (int i = 2, j = 1; i <= n; i++, j++) {
        while (j && a[i] != a[j]) j = nxt[j - 1][1] + 1;
        nxt[i][1] = j;
    }
    if (n % (n - nxt[n][0]) != 0 || nxt[n][0] < n / 2) {
        cout << "1\n1\n";
        return 0;
    } else if (n - nxt[n][0] == 1) {
        cout << n << "\n" << 1 << "\n";
        return 0;
    }
    int ans = 0;
    for (int i = 1; i * 2 <= n; i++) swap(nxt[i][1], nxt[n - i + 1][1]);
    for (int i = 1; i < n; i++)
        ans += (((i % (i - nxt[i][0]) != 0) || nxt[i][0] * 2 < i) &&
                ((n - i) % (n - i - nxt[i + 1][1]) != 0 || nxt[i + 1][1] * 2 < (n - i)));
    cout << 2 << "\n" << ans << "\n";
    return 0;
}
posted @ 2024-01-21 00:02  forgotmyhandle  阅读(20)  评论(0)    收藏  举报