20240402

T1

// 应该是 Topcoder 的题,但是实在搜不到,题目名太不具有辨识度

设每个朝代与标准纪年法的距离为 \(x_i\),则每个约束条件相当于限制了某两个 \(x_i\) 之间的差。查询的时候也假设查询的那场战争发生,同样视为限制。按照差分约束建出图,如果图中存在负环说明新加的这个限制会导致不等式组无解,也就是这场战争不可能发生。我这里使用了 Bellman-Ford 判负环,spfa 当然也行。

代码
#include <iostream>
#include <string.h>
#include <cassert>
using namespace std;
int n;
int mcnt[30];
int l[30][15], r[30][15];
struct Edge {
    int u, v, w;
} E[200005];
int ecnt;
inline bool isUpper(char x) { return 'A' <= x && x <= 'Z'; }
inline bool isDigit(char x) { return '0' <= x && x <= '9'; }
void Deal(string s) {
    assert(s[0] != ' ');
    int ax = s[0] - 'A' + 1;
    int ay = s[1] - '0' + 1;
    int bx = s[3] - 'A' + 1;
    int by = s[4] - '0' + 1;
    int l1 = l[ax][ay], r1 = r[ax][ay];
    int l2 = l[bx][by], r2 = r[bx][by];
    E[++ecnt] = (Edge) { bx, ax, r2 - l1 };
    E[++ecnt] = (Edge) { ax, bx, r1 - l2 };
}
void ini() {
    int m;
    cin >> m;
    m++;
    getchar();
    string str;
    while (m--) {
        string s;
        getline(cin, s);
        str += s;
    }
    string tmp;
    for (int i = 0; i < (int)str.size(); i++) {
        if (isUpper(str[i]) || isDigit(str[i]) || str[i] == '-') 
            tmp += str[i];
        if (tmp.size() == 5) {
            Deal(tmp);
            tmp = "";
        }
    }
}
int dist[30];
bool NegaCycle() {
    memset(dist, 63, sizeof dist);
    bool upd = 0;
    dist[1] = 0;
    for (int i = 0; i <= n; i++) {
        upd = 0;
        for (int j = 1; j <= ecnt; j++) {
            if (dist[E[j].v] > dist[E[j].u] + E[j].w) 
                dist[E[j].v] = dist[E[j].u] + E[j].w, upd = 1;
        }
    }
    return upd;
}
string sss;
int main() {
    cin >> n;
    getline(cin, sss);
    for (int i = 1; i <= n; i++) {
        string str;
        getline(cin, str);
        str += ' ';
        int cur = 0;
        for (int j = 0; j < (int)str.size(); j++) {
            if (isDigit(str[j])) 
                cur = cur * 10 + str[j] - '0';
            else {
                l[i][++mcnt[i]] = cur;
                r[i][mcnt[i] - 1] = cur - 1;
                cur = 0;
            }
        }
    }
    ini();
    int q;
    cin >> q;
    while (q--) {
        string str;
        cin >> str;
        Deal(str);
        cout << (NegaCycle() ? "N" : "Y");
        ecnt -= 2;
    }
    cout << "\n";
    return 0;
}

T2

Topcoder SRM 585 div1 Medium - LISNumber

考虑从小往大加入每一种数。设 \(dp[i][j]\) 表示已经加入了 \(i\) 类数,形成了 \(j\) 个 LIS 的方案数。发现加入的每一个数都会贡献一个 LIS,除非加在已有的 LIS 后面。所以就枚举一下把多少个数扔到已有的 LIS 后面,然后就可以进行刷表转移了。

代码
#include <iostream>
#define int long long
using namespace std;
const int P = 1000000007;
int n;
int c[40];
int K;
int dp[45][1305];
int inv[1305], fac[1305], ifac[1305];
void Cpre(int asdf) {
    fac[0] = fac[1] = inv[0] = inv[1] = ifac[0] = ifac[1] = 1;
    for (int i = 2; i <= asdf; i++) {
        fac[i] = fac[i - 1] * i % P;
        inv[i] = (P - P / i) * inv[P % i] % P;
        ifac[i] = ifac[i - 1] * inv[i] % P;
    }
}
inline int C(int n, int m) { return (n < 0 || m < 0 || n < m ? 0 : fac[n] * ifac[m] % P * ifac[n - m] % P); }
signed main() {
    // freopen("data.in", "r", stdin);
    // freopen("data.out", "w", stdout);
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> c[i];
    cin >> K;
    Cpre(1296);
    dp[0][0] = 1;
    int s = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= K; j++) {
            int cur = c[i];
            for (int k = 1; k <= cur; k++) {
                for (int cnt = 0; cnt <= k; cnt++) {
                    int r = k - cnt;
                    int tmp = C(cur - 1, k - 1);
                    int p = cur - k;
                    dp[i][j + p + r] = (dp[i][j + p + r] + dp[i - 1][j] * tmp % P * C(j, cnt) % P * C(s - j + 1, r) % P) % P;
                }
            }
        }
        s += c[i];
    }
    cout << dp[n][K] << "\n";
    return 0;
}

T3

Topcoder SRM 561 div1 Medium - CirclesGame

首先发现圆之间的包含关系会构成一棵树。每次操作相当于选一个节点,封掉这个节点到根路径上的所有点。不能操作者输。直接考虑博弈论。状态就是某棵子树,然后观察到一个状态的某个后继状态就是在这个子树中选出一个点,然后把这个点到这棵子树的根上的点全部删掉所形成的所有连通块。会发现这其实是递归的一个组合博弈游戏。我们求出所有这些连通块的 SG 函数值,然后把它们异或起来就可以得到这个后继状态的 SG 函数值。然后再对这个状态枚举其所有后继状态,把这些后继状态的 SG 值取 mex 即为当前状态的 SG。最后把所有连通块的 SG 异或起来看是不是 \(0\) 就好了。

代码
#include <iostream>
#include <algorithm>
#include <cassert>
#define int long long
using namespace std;
int n;
struct C {
    int x, y, r;
} a[200005];
bool chk(int i, int j) { // a in b
    return (a[i].x - a[j].x) * (a[i].x - a[j].x) + (a[i].y - a[j].y) * (a[i].y - a[j].y) <= (a[i].r - a[j].r) * (a[i].r - a[j].r) && a[i].r < a[j].r;
}
int head[5005], to[200005], nxt[200005], ecnt;
void add(int u, int v) { to[++ecnt] = v, nxt[ecnt] = head[u], head[u] = ecnt; }
int in[5005];
int L[5005], ncnt, R[5005];
int _dfn[5005];
int fa[5005];
void dfs1(int x) {
    _dfn[L[x] = ++ncnt] = x;
    for (int i = head[x]; i; i = nxt[i]) {
        fa[to[i]] = x;
        dfs1(to[i]);
    }
    R[x] = ncnt;
}
int f[5005];
bool vis[5005];
int cnt[10005];
void dfs2(int x) {
    for (int i = head[x]; i; i = nxt[i]) {
        int v = to[i];
        dfs2(v);
    }
    for (int i = L[x]; i <= R[x]; i++) {
        int tmp = 0;
        int v = _dfn[i], lst = -1;
        while (v != fa[x]) {
            for (int j = head[v]; j; j = nxt[j]) {
                if (to[j] != lst) 
                    tmp ^= f[to[j]];
            }
            lst = v;
            v = fa[v];
        }
        cnt[tmp]++;
    }
    for (int i = 0; i <= 1000; i++) {
        if (cnt[i] == 0) {
            f[x] = i;
            break;
        }
    }
    for (int i = 0; i <= 1000; i++) cnt[i] = 0;
}
int g[505][505];
signed main() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i].x;
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i].y;
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i].r;
    sort(a + 1, a + n + 1, [](C a, C b) { return a.r > b.r; });
    for (int i = 1; i <= n; i++) {
        for (int j = i - 1; j; j--) {
            if (chk(i, j)) {
                add(j, i);
                in[i]++;
                break;
            }
        }
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        if (!in[i]) {
            dfs1(i);
            dfs2(i);
            ans ^= f[i];
        }
    }
    cout << (ans ? "Alice" : "Bob") << "\n";
    return 0;
}

T4

Topcoder SRM 577 div1 Medium - EllysChessboard

考虑把整个过程倒过来,则就是每次删掉距离最远的两个点中的一个。使用状压 + 爆搜 + 记忆化即可。

代码
#include <iostream>
#include <map>
using namespace std;
int n;
string str[10];
int x[70], y[70];
long long dist[70][70];
map<unsigned long long, int> mp;
long long dfs(unsigned long long S) {
    if (S == (S & -S)) 
        return 0;
    if (mp.count(S)) 
        return mp[S];
    long long tmp = 0;
    int a = -1, b = -1;
    for (int i = 0; i <= n; i++) {
        for (int j = 0; j <= n; j++) {
            if (i == j || !((S >> i) & 1) || !((S >> j) & 1)) 
                continue;
            if (dist[i][j] > tmp) 
                tmp = dist[i][j], a = i, b = j;
        }
    }
    if (a == -1) 
        return 0;
    return mp[S] = min(dfs(S ^ (1ull << a)), dfs(S ^ (1ull << b))) + tmp;
}
int main() {
    cin >> n;
    n = -1;
    for (int i = 1; i <= 8; i++) {
        string str;
        cin >> str;
        for (int j = 0; j < 8; j++) {
            if (str[j] == '#') {
                ++n;
                x[n] = i, y[n] = j + 1;
            }
        }
    }
    for (int i = 0; i <= n; i++) {
        for (int j = 0; j <= n; j++) {
            if (i != j) 
                dist[i][j] = abs(x[i] - x[j]) + abs(y[i] - y[j]);
        }
    }
    cout << dfs((unsigned long long)(((__int128)1 << (n + 1)) - 1)) << "\n";
    return 0;
}

T5

Topcoder SRM 579 div1 Hard - RockPaperScissors

对于一个局面,我们能知道的信息只有当前一共投出了几个石头剪刀布。我们要根据这个信息进行决策。设 \(dp[a][b][c][0 / 1 / 2]\) 表示当前已经投出了 \(a\) 个石头,\(b\) 个剪刀,\(c\) 个布,而且接下来投出石头 / 剪刀 / 布的概率。这样我们就可以算出当前局面下出每种手势的期望收益。接下来考虑 dp 转移。我们枚举每个骰子,然后枚举所有局面,用当前骰子来转移当前局面。我们要算当前有 \(a,b,c\) 个石头剪刀布而且下一步投出石头 / 剪刀 / 布的概率,但是当前骰子并不一定就是当前要投的骰子。所以还要考虑当前骰子在之前被投出的情况。由于在之前被投出,所以并不影响下一步出什么,只影响之前投出石头剪刀布的个数。然后再考虑当前骰子在这一轮被投出的情况。我们再加一个 \(dp[a][b][c][3]\) 表示一共投了 \(a + b + c\) 个骰子,投出了 \(a, b, c\) 个石头剪刀布的概率,这样就可以直接枚举当前骰子出的结果,然后进行转移。然后考虑如何计算答案。由于期望的线性性质,我们可以对于每个局面把最优期望收益乘上这个局面出现的概率加起来,就是答案。先算出每个手势的期望收益,然后取最大值,这就是这个局面的最优期望收益。然后对于概率,我们会发现对于 \(a + b + c\) 相同的局面,其出现概率都是相等的,也就是在 \(n\) 个骰子中选出 \(a + b + c\) 个排在最前面,而选中的恰好是当前这 \(a + b + c\) 个骰子的概率 乘上 接下来选中的恰好是特定的这一个骰子的概率,也就是 \(\frac{1}{\binom{n}{a + b + c}} \times \frac{1}{n - a - b - c}\)

代码
#include <iostream>
#include <string.h>
#include <iomanip>
using namespace std;
int n;
int x[55], y[55], z[55];
double f[55][55][55][4];
double g[55][55][55][4];
double C[55][55];
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> x[i];
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> y[i];
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> z[i];
    C[0][0] = C[1][1] = C[1][0] = 1;
    for (int i = 2; i <= n; i++) {
        C[i][0] = 1;
        for (int j = 1; j <= i; j++) 
            C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
    }
    f[0][0][0][3] = 1;
    for (int i = 1; i <= n; i++) {
        double p1 = x[i] / 300.0, p2 = y[i] / 300.0, p3 = z[i] / 300.0;
        memcpy(g, f, sizeof f);
        for (int a = 0; a <= i; a++) {
            for (int b = 0; a + b <= i; b++) {
                for (int c = 0; a + b + c <= i; c++) {
                    for (int d = 0; d < 3; d++) {
                        // 当前枚举的骰子不是此时投的
                        f[a + 1][b][c][d] += g[a][b][c][d] * p1;
                        f[a][b + 1][c][d] += g[a][b][c][d] * p2;
                        f[a][b][c + 1][d] += g[a][b][c][d] * p3;
                    }
                    // 当前枚举的骰子是此时投的
                    f[a][b][c][0] += g[a][b][c][3] * p1;
                    f[a][b][c][1] += g[a][b][c][3] * p2;
                    f[a][b][c][2] += g[a][b][c][3] * p3;
                    // 总方案数
                    f[a + 1][b][c][3] += g[a][b][c][3] * p1;
                    f[a][b + 1][c][3] += g[a][b][c][3] * p2;
                    f[a][b][c + 1][3] += g[a][b][c][3] * p3;
                }
            }
        }
    }
    double ans = 0;
    for (int a = 0; a < n; a++) {
        for (int b = 0; a + b < n; b++) {
            for (int c = 0; a + b + c < n; c++) {
                double e1 = f[a][b][c][0] * 3 + f[a][b][c][1];
                double e2 = f[a][b][c][1] * 3 + f[a][b][c][2];
                double e3 = f[a][b][c][2] * 3 + f[a][b][c][0];
                // 期望乘概率
                ans += max(e1, max(e2, e3)) / C[n][a + b + c] / (n - a - b - c);
            }
        }
    }
    cout << setprecision(10) << ans << "\n";
    return 0;
}

考虑使用反证法(?)判断询问。

观察贡献条件,把思路理清再写。

对于代码中所有地方,会写正常做法的就不要写乱搞。

暴力出奇迹。

dp设计状态可以考虑把当前能够知道的扔进状态里。

posted @ 2024-04-03 00:26  forgotmyhandle  阅读(13)  评论(0编辑  收藏  举报