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。观察性质,猜结论。

posted @ 2024-04-11 15:06  forgotmyhandle  阅读(31)  评论(0)    收藏  举报