20240409
T1
Topcoder SRM 593 div2 Hard - MayTheBestPetWin
由于每个宠物都要被分到一组中,所以只需要知道一组中的 \(\sum mx - \sum mn\) 就可以推出另一组的 \(\sum mx - \sum mn\)。然后直接背包 dp 即可。
代码
#include <iostream>
using namespace std;
const int N = 500000;
int dp[51][1000005];
int n;
int mx[55], mn[55], Mx, Mn;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> mn[i], Mn += mn[i];
cin >> n;
for (int i = 1; i <= n; i++) cin >> mx[i], Mx += mx[i];
dp[0][N] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= N + N; j++) {
if (j >= mx[i])
dp[i][j] |= dp[i - 1][j - mx[i]];
if (j + mn[i] <= N + N)
dp[i][j] |= dp[i - 1][j + mn[i]];
}
}
int ans = 2147483647;
for (int i = 0; i <= N + N; i++) {
if (dp[n][i]) {
int x = i - N;
ans = min(ans, max(abs(x), abs(-(x - Mx + Mn))));
}
}
cout << ans << "\n";
return 0;
}
T2
Topcoder SRM 600 div1 Medium - PalindromeMatrix
先二进制枚举出强制哪些列回文,然后考虑行。把上下两个对称的行一块考虑,令 \(cst_{i, 0/1/2}\) 表示第 \(i\) 行和第 \(n - i + 1\) 行中保证所有枚举到的列在这两行上的数相等且这两行中有 \(0 / 1 / 2\) 行回文的最小代价。然后就可以背包 dp 求出有至少 \(rc\) 行回文的最小代价。算 \(cst\) 就枚举列然后分讨。
代码
#include <iostream>
#include <string.h>
using namespace std;
int n, m, cc, cr;
string str[15];
int dp[15][15];
int calc(int S) {
memset(dp, 63, sizeof dp);
dp[0][0] = 0;
int ret = 2147483647;
for (int i = 1; i <= n / 2; i++) {
int x = i, y = n + 1 - i;
int zero = 0, one = 0, two = 0;
for (int j = 1; j <= m / 2; j++) {
int a = j, b = m + 1 - j;
int i1 = ((S >> (a - 1)) & 1), i2 = ((S >> (b - 1)) & 1);
zero += (i1 && (str[x][a] != str[y][a])) + (i2 && (str[x][b] != str[y][b]));
if (!(i1) && !(i2))
two += ((str[x][a] != str[x][b]) + (str[y][a] != str[y][b]));
else
two += min(str[x][a] + str[x][b] + str[y][a] + str[y][b], 4 - (str[x][a] + str[x][b] + str[y][a] + str[y][b]));
}
int t1 = 0, t2 = 0;
for (int j = 1; j <= m / 2; j++) {
int a = j, b = m + 1 - j;
int i1 = ((S >> (a - 1)) & 1), i2 = ((S >> (b - 1)) & 1);
if (i1 && i2) {
t1 += min(str[x][a] + str[x][b] + str[y][a] + str[y][b], 4 - (str[x][a] + str[x][b] + str[y][a] + str[y][b]));
t2 += min(str[x][a] + str[x][b] + str[y][a] + str[y][b], 4 - (str[x][a] + str[x][b] + str[y][a] + str[y][b]));
} else if ((!i1) && (!i2)) {
t1 += str[x][a] != str[x][b];
t2 += str[y][a] != str[y][b];
} else if (i1) {
t1 += min(str[x][a] + str[x][b] + str[y][a], 3 - (str[x][a] + str[x][b] + str[y][a]));
t2 += min(str[x][a] + str[y][a] + str[y][b], 3 - (str[x][a] + str[y][a] + str[y][b]));
} else {
t1 += min(str[x][a] + str[x][b] + str[y][b], 3 - (str[x][a] + str[x][b] + str[y][b]));
t2 += min(str[x][b] + str[y][a] + str[y][b], 3 - (str[x][b] + str[y][a] + str[y][b]));
}
}
one = min(t1, t2);
for (int j = 0; j <= i * 2; j++) {
dp[i][j] = min(dp[i][j], dp[i - 1][j] + zero);
if (j >= 1)
dp[i][j] = min(dp[i][j], dp[i - 1][j - 1] + one);
if (j >= 2)
dp[i][j] = min(dp[i][j], dp[i - 1][j - 2] + two);
}
}
for (int i = cr; i <= n; i++) ret = min(ret, dp[n / 2][i]);
return ret;
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> str[i], str[i] = ' ' + str[i];
m = (int)str[1].size() - 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++)
str[i][j] -= '0';
}
cin >> cr >> cc;
int ans = 2147483647;
for (int i = 0; i < (1 << m); i++) {
if (__builtin_popcount(i) >= cc)
ans = min(ans, calc(i));
}
cout << ans << "\n";
return 0;
}
T3
Topcoder SRM 584 div1 Medium - Excavations
考虑一个 \(k\) 元组合法的条件,设 \(mindep_x\) 表示所有选出的第 \(x\) 种建筑中最小的深度,也就是 \(\max\limits_{u \in found} mindep_u < \min\limits _{v \notin found}mindep_v\),也就是将探测深度设为 \(\max\limits_{u \in found} mindep_u\),然后每一种建筑都能探到深度最小的。所以可以枚举这个探测深度是哪个建筑取到的,然后进行 \(dp[i]\) 表示选了 \(i\) 个建筑的方案数。枚举每一种建筑转移,对于不在 \(found\) 中的建筑类型,可以在大于当前深度的建筑里任选。对于 \(found\) 中的建筑类型,要求必须在小于当前深度的建筑中选至少一个。然后转移即可。
代码
#include <iostream>
#include <algorithm>
#include <string.h>
#define int long long
using namespace std;
int n, m, K;
struct node {
int d, t;
} a[55];
bool vis[55];
int cnt[55], cur[55];
int f[55], g[55];
int C[55][55];
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i].t, cnt[a[i].t]++;
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i].d;
cin >> m;
for (int i = 1, x; i <= m; i++) cin >> x, vis[x] = 1;
cin >> K;
sort(a + 1, a + n + 1, [](node a, node b) { return (a.d == b.d) ? (vis[a.t] < vis[b.t]) : (a.d < b.d); });
C[0][0] = C[1][0] = C[1][1] = 1;
for (int i = 2; i <= 50; i++) {
for (int j = 1; j <= i; j++)
C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
C[i][0] = 1;
}
int ans = 0;
for (int i = 1; i <= n; i++) {
cnt[a[i].t]--;
if (!vis[a[i].t])
continue;
memset(f, 0, sizeof f);
f[1] = 1;
for (int j = 1; j <= 50; j++) {
memcpy(g, f, sizeof f);
memset(f, 0, sizeof f);
for (int k = 1; k <= K; k++) {
if (g[k]) {
for (int x = 0; x <= cnt[j] && x + k <= K; x++)
f[k + x] += g[k] * C[cnt[j]][x];
}
}
if (!vis[j] || a[i].t == j)
continue;
memcpy(g, f, sizeof f);
memset(f, 0, sizeof f);
for (int k = 1; k <= K; k++) {
if (g[k]) {
for (int x = 1; x <= cur[j] && x + k <= K; x++)
f[k + x] += g[k] * C[cur[j]][x];
}
}
}
ans += f[K];
cur[a[i].t]++;
}
cout << ans << "\n";
return 0;
}
T4
Topcoder SRM 599 div1 Medium - FindPolygons
首先奇数无解。其次如果 \(4 | n\),则一个正方形会使得答案为 \(0\)。否则一个矩形会使得答案为 \(1\)。于是接下来只需要找是否有满足条件的三角形。有一个结论,一个三边长和面积都为整数的三角形可以是格点三角形,所以只需要枚举边长算一下即可。不会证,有会证的可以在评论区讨论。
代码
#include <iostream>
#include <math.h>
#define int long long
using namespace std;
signed main() {
int n;
cin >> n;
if ((n & 1) || n == 2) {
cout << "-1\n";
return 0;
}
int ans = 21457483647;
for (int i = 1; i < n; i++) {
for (int j = i; i + j < n; j++) {
int k = n - i - j;
if (k < j)
continue;
if (i + j <= k)
continue;
int tmp = (i + j + k) * (-i + j + k) * (i - j + k) * (i + j - k);
int r = sqrt(tmp);
if (r * r == tmp)
ans = min(ans, max(i, max(j, k)) - min(i, min(j, k)));
}
}
if (ans != 21457483647)
cout << ans << "\n";
else
cout << (n % 4 != 0) << "\n";
return 0;
}
T5
Topcoder SRM 593 div1 Hard - WolfDelaymasterHard
首先有 \(dp[i]\) 表示前 \(i\) 个字符构成合法串的方案数。填表法,考虑可以转移到哪些位置。首先当前位置到中点不能有 \(\texttt{o}\),其次中点到最后不能有 \(\texttt{w}\)。观察到一定是两个连续的 \(\texttt{w}\) 之间作为后一段,前一个 \(\texttt{w}\) 到起点作为前一段,因此可以发现合法的转移区间个数一定不超过 \(\log\) 级别。因此可以拿一个指针维护当前走到的中点,然后每次找这个中点对应的终点左边的第一个 \(\texttt{w}\)。如果这个 \(\texttt{w}\) 在中点之左,则可以转移,转移之后中点跳到中点右边的第一个 \(\texttt{w}\)。否则中点跳到中点左边的第一个 \(\texttt{w}\)。要注意跳和转移的时候不能超过起点右边的第一个 \(\texttt{o}\)。这样跳的次数不会超过 \(\log\) 次,总复杂度 \(\mathcal{O}(n\log n)\),可以通过。
代码
#include <iostream>
#define int long long
using namespace std;
const int P = 1000000007;
int n;
string s;
void input() {
long long wlen, olen, w0, wmul, wadd, omul, oadd, o0;
cin >> n >> wlen >> w0 >> wmul >> wadd >> olen >> o0 >> omul >> oadd;
for (int i = 0 ; i < n; i++) s += '?';
long long x = w0;
for (int i = 0 ; i < wlen; i++){
s[x] = 'w';
x = (x * wmul + wadd) % n;
}
x = o0;
for (int i = 0 ; i < olen; i++){
s[x] = 'o';
x = (x * omul + oadd) % n;
}
}
int rec[4000005][3];
int dp[4000005];
int pre[4000005];
signed main() {
input();
s = ' ' + s;
for (int i = 1; i <= n * 2; i++) rec[i][1] = rec[i][2] = n + 1;
for (int i = 1; i <= n; i++) rec[i][0] = (s[i] == 'w' ? i : rec[i - 1][0]);
for (int i = n; i; i--) {
rec[i][1] = (s[i] == 'w' ? i : rec[i + 1][1]);
rec[i][2] = (s[i] == 'o' ? i : rec[i + 1][2]);
}
dp[0] = 1;
for (int i = 0; i <= n; i++) {
i ? (pre[i] = (pre[i] + pre[i - 1]) % P) : (dp[0] = 1);
dp[i] = (dp[i] + pre[i]) % P;
if (i & 1)
continue;
int x = i + 1, e = rec[x][2];
while (x < e) {
if (rec[x * 2 - i][0] <= x) {
int t = rec[x * 2 - i][1];
pre[x * 2 - i] = (pre[x * 2 - i] + dp[i]) % P;
pre[min((e - 1) * 2 - i + 1, t)] = (pre[min((e - 1) * 2 - i + 1, t)] + P - dp[i]) % P;
x = t;
} else
x = rec[x * 2 - i][0];
}
}
cout << dp[n] << "\n";
return 0;
}
先枚举,后 dp。观察性质,猜结论。

浙公网安备 33010602011771号