USACO 25FEB 铜组题解
T1
大模拟。
思路挺简单的,就是调试比较麻烦。
初始化部分:
- 只需要处理左上角四分之一的点 \([r, c]\),并计算它与对称点的
#数量记为 \(cnt_{r, c}\)。 - 目标是使四个对称点的字符保持相同(全是
.或全是#)并计算 \(\min(cnt_{r, c}, 4 - cnt_{r, c})\) 来找到最少修改次数。
模拟修改部分:
对于每次修改,如果是将 # 改为 .,就 \(cnt_{x, y} - 1\),反之 \(+1\)。
等于是重做初始化部分第一步。
然后将新计算出的答案加入即可。
\(ans \gets ans + \min(cnt_{r,c}, 4 - cnt_{r,c}) - cnt_{r,c}'\)
注:\(cnt_{r,c}'\) 是上一次计算出的 \(cnt_{r,c}\) 值。
这条代码的意思是,在改变一个字符后(坐标设为 \([r,c]\))重新计算 \(cnt_{r,c}\) 与原先 \(cnt_{r,c}'\) 进行对比然后将计算出来多或少的值更新至 \(ans\) 中。
注:由于有特判,这个修改的字符绝对是在左上角部分。
例如:
#.
..
初始化部分的 \(ans\) 计算为 \(1\),假设修改左上角 # 至 .;
..
..
更新 \(cnt_{1,1} \gets cnt_{1, 1}-1\),此时根据修改后的 \(cnt_{1,1}\)(值为 \(0\)),修改 \(ans\) 值,\(ans \gets ans + \min(0, 4-0) - 1\),\(ans\) 修改值为 \(0\)。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
int n, u;
string mp[2005];
int main()
{
cin >> n >> u;
for (int i = 0; i < n; i++)
cin >> mp[i];
int cnt[2005][2005];
memset(cnt, 0, sizeof(cnt));
ll ans = 0;
for (int r = 0; r < n / 2; r++)
{
for (int c = 0; c < n / 2; c++)
{
if (mp[r][c] == '#')
cnt[r][c]++;
if (mp[r][n - 1 - c] == '#')
cnt[r][c]++;
if (mp[n - 1 - r][c] == '#')
cnt[r][c]++;
if (mp[n - 1 - r][n - 1 - c] == '#')
cnt[r][c]++;
ans += min(cnt[r][c], 4 - cnt[r][c]);
}
}
cout << ans << "\n";
for (int i = 0; i < u; i++)
{
int r, c;
cin >> r >> c;
int rr = min(r - 1, n - r);
int cc = min(c - 1, n - c);
int x = min(cnt[rr][cc], 4 - cnt[rr][cc]);
if (mp[r - 1][c - 1] == '#')
{
mp[r - 1][c - 1] = '.';
cnt[rr][cc]--;
}
else
{
mp[r - 1][c - 1] = '#';
cnt[rr][cc]++;
}
ans += min(cnt[rr][cc], 4 - cnt[rr][cc]) - x;
cout << ans << "\n";
}
return 0;
}
T2
思路
要让数组 \(a\) 中的 \(mex = i\),我们需要让数组 \(a\) 满足两个条件:
- \(\forall x \in \{0, 1, \dots, i-1\}, \quad x \in a\)
- \(i \notin a\)
对于代码实现,我们可以先预处理一个数组 \(cnt\),\(cnt_i\) 表示数组 \(a\) 中数字 \(i\) 的个数。
对于每个 \(mex\) 的询问,我们先遍历 \(0 \sim i - 1\),查看有多少个数缺失,设为 \(miss\)(条件 1);然后再调取 \(cnt_i\) 查看有多少它本身在数组 \(a\)(条件 2)。
此时,我们已经得到了解法,但是这种解法在每次解决 \(\forall x \in \{0, 1, \dots, n\}, \quad x = mex\) 时都要重新计算 \(miss\),时间复杂度 \(O(n^2)\),显然过不了 \(2 \times 10^5\),我们需要优化。
而显然,\(miss\) 可以预处理进行优化,具体思路类似递推或 dp。
设 \(miss_i\) 为 \(0 \sim i - 1\) 中有多少个数缺失,而 \(miss_i\) 可以从 \(miss_{i - 1}\) 转移:
\( miss_i = \begin{cases} miss_{i-1} + 1, & \text{if } cnt_{i-1} \neq 0 \\ miss_{i-1}, & \text{if } cnt_{i-1} = 0 \end{cases} \)
时间复杂度约为 \(O(n)\),可以通过。
代码
#include <bits/stdc++.h>
using namespace std;
int n;
vector<int> a;
int cnt[200005];
int miss[200005];
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
int x;
cin >> x;
a.push_back(x);
cnt[x]++;
}
sort(a.begin(), a.end());
for (int i = 1; i <= n; i++)
{
if (cnt[i - 1] == 0)
miss[i] = miss[i - 1] + 1;
else
miss[i] = miss[i - 1];
}
int q = 0;
while (q <= n)
{
int now = cnt[q] - miss[q];
if (now >= 0)
cout << cnt[q] << "\n";
else
cout << cnt[q] + abs(now) << "\n";
q++;
}
return 0;
}
T3
思路
本题解使用简单 dp 解决本问题。
设 \(dp_{i, j}\) 为区间 \([i, j]\) 中使用的 print 代码个数,判断 \(\text{if } dp_{1, n} \le k\)。
对于长度为 \(1\) 的子序列,\(dp_{i, i} = 1\),指使用一个 print 语句输出字符。
对于长度大于 \(1\) 的子序列,设子序列长度为 \(len\),从 \(2 \sim n\);左右边界为 \([i, j],1 \le i \le n - len + 1\):
- 在子序列中遍历一个可能的分隔点,设为 \(l\),\(i \leq l \leq j\);初始化 \(dp_{i, j} = \min(dp_{i, j}, dp_{i, l} + dp_{l + 1, j})\)。
- 然后开始检查子序列是否可以由子序列中的一个子序列拼接而来。
- 枚举子序列中的子序列的长度,即周期长度,设为 \(l\),\(1 \le l < len\) 且 \(len \bmod l = 0\)。
- 检查周期中每个位置 \(p\),\(i \le p \leq j\),是否满足 \(a_p = a_{i + (p - i) \bmod l}\),即为检查子序列中每 \(l\) 位是否与周期 \(p_{i} \sim p_{i + l - 1}\) 相同。如果全部满足,则更新 \(dp_{i, j} = \min(dp_{i, j}, dp_{i, i + l - 1})\)。
输出:判断 \(dp_{1, n} \le k\) 即可。
时间复杂度约为 \(O(T \times n^3 \times f(n))\) 左右,其中 \(f(n)\) 表示 \(n\) 的因子数量。
代码
#include <bits/stdc++.h>
using namespace std;
int t;
int n, k;
int main()
{
int t;
cin >> t;
while (t--)
{
int n, k;
cin >> n >> k;
int a[105];
for (int i = 1; i <= n; i++)
cin >> a[i];
int dp[105][105];
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
dp[i][j] = 105;
}
for (int i = 1; i <= n; i++)
dp[i][i] = 1;
for (int len = 2; len <= n; len++)
{
for (int i = 1; i <= n - len + 1; i++)
{
int j = i + len - 1;
for (int l = i; l < j; l++)
dp[i][j] = min(dp[i][j], dp[i][l] + dp[l + 1][j]);
int x = len;
for (int l = 1; l < x; l++)
{
if (x % l != 0)
continue;
bool flag = true;
for (int p = i; p <= j; p++)
{
if (a[p] != a[i + (p - i) % l])
{
flag = false;
break;
}
}
if (flag)
dp[i][j] = min(dp[i][j], dp[i][i + l - 1]);
}
}
}
cout << (dp[1][n] <= k ? "YES" : "NO") << "\n";
}
return 0;
}
作者:George0915,转载请注明原文链接:https://www.cnblogs.com/George222/p/18787677

浙公网安备 33010602011771号