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;
}