Atcoder Regular Contest 060 题解
ARC060C. Tak and Cards *1583
简单题。考虑一个非常非常常见的 Trick。把区间平均值为 \(k\) 转化为区间和为 \(0\) 只需要将每个数都减去 \(k\) 即可。然后就是一个朴素的背包求和为 \(0\) 方案数。注意处理负数下标就好了。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MAXN = 55;
LL n, a, A[MAXN], DP[MAXN][5005], delta = 2500;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> a;
for(LL i = 1; i <= n; i ++)
{
cin >> A[i];
A[i] -= a;
}
DP[0][delta] = 1;
for(int i = 1; i <= n; i ++)
{
for(int j = 0; j <= 5000; j ++)
{
if(j >= A[i]) DP[i][j] = DP[i - 1][j] + DP[i - 1][j - A[i]];
else DP[i][j] = DP[i - 1][j];
}
}
cout << DP[n][delta] - 1 << '\n';
return 0;
}
ARC060D. Digit Sum *2261
非常神的题,但是我自己想到了。
根号分治。首先如果 \(b \leq \sqrt{n}\),那么直接暴力枚举求得数位和是否为 \(s\)。
如果 \(b > \sqrt{n}\),我们注意到有一个结论,其最小的可能的进制就是 \(\frac{n-s}{i}+1\)。注意这里要保证 \(n-s\) 是 \(i\) 的倍数。记 \(p=\frac{n-s}{i}+1\),最后还要判断一下 \(f(p,n)\) 是否等于 \(s\)。然后我们就做完了。注意要特判一下,如果这里的 \(p=1\),那么我们直接扔掉。不扔掉的话会死循环寄掉。
然后做完了,代码不长但根号分治一般都很神。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL n, s;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> s;
if(n < s)
{
cout << -1 << '\n';
return 0;
}
for(LL i = 2; i <= (LL)sqrt(n); i ++)
{
LL sum = 0, nn = n;
while(nn > 0)
{
sum += (nn % i);
nn /= i;
}
if(sum == s)
{
cout << i << '\n';
return 0;
}
}
LL minnum = 1e15 + 7;
if(s == n) minnum = min(minnum, n + 1);
for(LL i = 1; i <= sqrt(n); i ++)
{
if((n - s) % i != 0) continue;
LL b = (n - s) / i + 1;
if(b == 1) continue;
LL sum = 0, nn = n;
while(nn > 0)
{
sum += (nn % b);
nn /= b;
}
if(sum == s) minnum = min(minnum, b);
}
if(minnum == 1e15 + 7) cout << -1 << '\n';
else cout << minnum << '\n';
return 0;
}
ARC060E. Tak and Hotels *2154
倍增优化动态规划,我应该还是第一次理解这个东西。朴素的暴力就是暴力枚举,这里不做过多讲解。
考虑正解,设 \(f_{i,j}\) 为从 \(i\) 跳 \(2^j\) 步能跳到的最远点。然后每次 Query 的时候你都往上调,基本就类似最近公共祖先 LCA 的那种求法随便倍增。然后就做完了。注意一下处理 \(f_{i,0}\) 的细节。还有数组空间别开小(我就因为这个赛时没过 E 题)
然后就做完了。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MAXN = 100005;
LL n, A[MAXN], L, Q, DP[MAXN][25];
LL Query(LL a, LL b)
{
LL now = a, ans = 0;
for(LL i = 24; i >= 0; i --)
if(DP[now][i] < b)
{
ans += (1 << i);
now = DP[now][i];
}
return ans + 1;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n;
for(LL i = 1; i <= n; i ++) cin >> A[i];
cin >> L;
memset(DP, 0x3f, sizeof(DP));
for(LL i = 1; i <= n; i ++)
{
LL Pos = upper_bound(A + 1, A + n + 1, A[i] + L) - A - 1;
DP[i][0] = Pos;
}
for(LL j = 1; j <= 24; j ++)
for(LL i = 1; i <= n; i ++)
DP[i][j] = DP[DP[i][j - 1]][j - 1];
cin >> Q;
while(Q --)
{
LL a, b;
cin >> a >> b;
if(a > b) swap(a, b);
cout << Query(a, b) << '\n';
}
return 0;
}
ARC060F. Best Representation *2804
Subtask 1:\(|s| \leq 4000\)
考虑一个 \(O(n^2)\) 的做法。看到划分想到动态规划。这个应该是简单的,设 \(f_i\) 表示前 \(i\) 个能划分的最少子串个数。\(g_i\) 表示划分成最少子串个数的方案数。但是我们需要一个 \(Q_{i,j}\) 表示 \(i\) 到 \(j\) 的子串是否是好串。
注意到好串的定义就是之前题目里面的 Power Strings。然后就递推跑 KMP,应该是跑 \(n\) 遍 KMP,每次跑 \(i\) 到 \(n\) 这个子串,预处理出 \(Q_{i,j}\)。动态规划的时间复杂度是 \(O(n^2)\),跑 \(n\) 遍 KMP 的时间复杂度也是 \(O(n^2)\),所以总复杂度就是 \(O(n^2)\),然后就做完了。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 4005;
const int MOD = 1e9 + 7;
string s;
int n, KMP[MAXN], Q[MAXN][MAXN], f[MAXN], g[MAXN];
int main()
{
cin >> s; n = s.length(); s = ' ' + s;
for(int i = 1; i <= n; i ++)
for(int j = i; j <= n; j ++)
Q[i][j] = 1;
for(int i = 1; i <= n; i ++)
{
memset(KMP, 0, sizeof(KMP));
int k = 0;
for(int j = 2; j <= n - i + 1; j ++)
{
while(k > 0 && s[j + i - 1] != s[k + i]) k = KMP[k];
if(s[j + i - 1] == s[k + i]) k ++;
KMP[j] = k;
if(j % (j - KMP[j]) == 0 && j / (j - KMP[j]) >= 2) Q[i][i + j - 1] = 0;
}
}
memset(f, 0x3f, sizeof(f));
f[0] = 0;
for(int i = 1; i <= n; i ++)
{
for(int j = 0; j < i; j ++)
{
if(Q[j + 1][i] == 0) continue;
if(f[j] + 1 == f[i]) g[i] = (g[i] + 1) % MOD;
else if(f[j] + 1 < f[i])
{
f[i] = f[j] + 1;
g[i] = 1;
}
}
}
cout << f[n] << '\n' << g[n] << '\n';
return 0;
}
Subtask 2:\(|s| \leq 5\times 10^5\)
首先特判:
- 如果字符串内的元素全部相等,那么必然要划分成 \(n\) 个串,个数就是 \(1\) 个。
- 如果字符串没有长度 \(>1\) 的周期或者 \(|s|=1\),那么可以不用划分,最小划分个数就是 \(1\),个数也是 \(1\)。
后面有一个非常强的结论,我不想证。就是如果存在周期长度 \(>1\),那么一定可以从第一个字符后面切开,使这个字符串变为两个串,那么这两个串一定不是 Power Strings。这点通过对于暴力 \(f\) 数组的打表也可以发现,所以这种情况的最小划分个数就是 \(2\)。
然后怎么计数呢?因为是划分成两段,所以我们很自然的想到枚举分界点,然后看前面一段是不是 Power Strings,然后看后面一段是不是 Power Strings,因为 KMP 跑出的 border 具有对称性,所以可以通过正反串两次 KMP 得出答案。最后判断是 \(O(1)\) 的,上面讲过怎么判断 Power Strings。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 500005;
string s, t;
int n, KMP[MAXN], KMP2[MAXN];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> s; n = s.length(); s = ' ' + s;
bool Flag = true;
for(int i = 2; i <= n; i ++) if(s[i] != s[i - 1]) Flag = false;
if(Flag)
{
cout << n << '\n' << 1 << '\n';
return 0;
}
for(int i = n; i >= 1; i --) t += s[i];
t = ' ' + t;
for(int i = 2, j = 0; i <= n; i ++)
{
while(j > 0 && s[i] != s[j + 1]) j = KMP[j];
if(s[i] == s[j + 1]) j ++;
KMP[i] = j;
}
for(int i = 2, j = 0; i <= n; i ++)
{
while(j > 0 && t[i] != t[j + 1]) j = KMP2[j];
if(t[i] == t[j + 1]) j ++;
KMP2[i] = j;
}
if(!KMP[n] || n % (n - KMP[n]) != 0) // 没有循环节
{
cout << 1 << '\n' << 1 << '\n';
return 0;
}
int ans = 0;
for(int i = 1; i < n; i ++)
if((KMP[i] == 0 || i % (i - KMP[i]) != 0) && (KMP2[n - i] == 0 || (n - i) % (n - i - KMP2[n - i]) != 0)) ans ++;
cout << 2 << '\n' << ans << '\n';
return 0;
}