2019.10.22模拟赛

T1 合并集合

\(n\)个集合,第\(i\)个集合记为\(S_i\),集合按环状排列,即第\(i + 1\)个集合右边是第\(i\)个集合,第\(n\)个集合右边是第\(i\)个集合。
一开始每个集合里只有一个数,每次你可以选择两个相邻的集合\(S,T\),然后合并成\(S \cup T\),之后你可以获得收益\(|S| \times |T|\),其中\(|S|\)表示集合\(S\)的元素个数。
你需要一直进行以上的操作直到只剩一个集合为止,求能获得的最大的收益之和。

断环为链,复制两倍区间DP。

signed main() {
    cin >> n;
    for (register int i = 1; i <= n; ++i) cin >> a[i];
    for (register int i = 1; i <= n; ++i) a[i + n] = a[i];
    for (register int len = 1; len <= n; ++len) {
        for (register int l = 1, r = len + l - 1; r <= 2 * n; ++l, ++r) {
            cnt.reset();
            for (register int i = l; i <= r; ++i) cnt[a[i]] = 1;
            sum[l][r] = cnt.count();
            //          cerr << l << " " << r << " " << sum[l][r] << endl;
        }
    }
    for (register int len = 2; len <= n; ++len) {
        for (register int l = 1, r = l + len - 1; r <= 2 * n; ++l, ++r) {
            for (register int k = l; k < r; ++k) {
                f[l][r] = max(f[l][r], f[l][k] + f[k + 1][r] + sum[l][k] * sum[k + 1][r]);
            }
        }
    }
    int ans = 0;
    for (register int i = 1; i <= n; ++i) ans = max(ans, f[i][i + n - 1]);
    cerr << ans << endl;
    cout << ans << endl;
}

T2 爬

小 M 把小 D 困在了一个高度为\(L\)的陷阱里,小 D 有\(N\)天时间可以爬出来,他有\(N\)个药丸,每天吃一颗,顺序任意。
第 颗药丸可以让他早上爬\(A_i\),下午滑下去\(B_i\),如果他任何时候能爬到井口,就能爬出来。
小 M 每天晚上都会放水,第\(i\)天晚上水位会升高\(C_i\),如果小 D 不能在晚上保证严格高于水位,就会被淹死。
问小 D 最早第几天可以爬出来,如果无解输出\(-1\)

贪心 + 二分答案。
直接二分多少天能爬出来。
check的时候贪心的取能往上走的最大值。
之后在前mid天中找下滑最少的或者在后边的天中找上爬最大的作为最后一天的药丸。

inline bool judge(const int &X) {
    if (sum[X] >= L)
        return 1;
    register int pos = 1;
    for (register int i = 1; i < X; ++i)
        while (sum[i + 1] - med[pos].y <= a[i]) ++pos;
    int mx = 0;
    for (register int i = pos; i <= X; ++i) mx = max(med[i].y, mx);
    if (sum[X] + mx >= L)
        return 1;
    mx = 0;
    for (register int i = X + 1; i <= n; ++i) mx = max(med[i].x, mx);
    if (sum[X - 1] + mx >= L)
        return 1;
    return false;
}
signed main() {
    cin >> n >> L;
    for (register int i = 1; i <= n; ++i) cin >> med[i].x >> med[i].y;
    for (register int i = 1; i <= n; ++i) cin >> a[i], a[i] += a[i - 1];
    sort(med + 1, med + 1 + n, cmp);
    int pos = 1;
    for (register int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + med[i].x - med[i].y;
    for (; pos <= n; ++pos)
        if (sum[pos] <= a[pos])
            break;
    if (pos > n)
        --pos;
    register int l = 1, r = pos, res = -1, mid;
    while (l <= r) {
        mid = (l + r) >> 1;
        if (judge(mid))
            res = mid, r = mid - 1;
        else
            l = mid + 1;
    }
    cout << res << endl;
}

T3

小 M 和小 D 在玩游戏。
小 M 有个\(w\)\(h\)列的棋盘, 每个格子都包含了至多一枚硬币,硬币正面朝上或反面朝上。
小 M 和小 D 轮流操作,小 M 先手。每个人可以选择棋盘中没在之前被选择过的一行或一列,然然后将所选的行或列上的硬币全部翻转,即正面变成反面,反面变成正面。
当所有硬币都正面朝上或者所有的行列都被选择,游戏结束。最后一次操作的玩家将会获得 分。如果当前局面所有的硬币都正面朝上,小 M 和小 D 都将获得\(2\)分的额外收益。
问两个人如果都按最大化自己分数的策略操作,那么小 M 最后的得分是多少。
保证每列至少有一个硬币,每行至少有一个反面朝上的硬币。

这道题还是一道蛮有意思的博弈。
因为最大化收益,所以能使所有的硬币朝上就尽量让所有的硬币朝上。
如果不行,每个人每次取一行或一列,所以如果\(h + w\)是奇数,先手必胜,否则后手必胜。
之后考虑所有硬币朝上。当前硬币如果朝上,那么它将会被翻偶数次,否则他将会被翻奇数次。因此我们可以对行与列连边进行二分图染色。
(菜鸡的我这是第一次写二分图 染色抄了半天)
之后每一个联通块,都会出现\(a_i\)个左部节点和\(b_i\)个右部节点,当\(a_i\)\(b_i\)奇偶性不同会对结果造成影响,如果有奇数个这种情况,先手必胜。否则考虑奇偶性相同的操作的影响。

namespace ddd {
const int MAXN = 205;
int h, w;
struct node {
    int nxt, ver;
    bool edge;
} e[MAXN * MAXN];
int head[MAXN], tot;
inline void add(const int &x, const int &y, const bool &z) {
    e[++tot].ver = y;
    e[tot].nxt = head[x];
    e[tot].edge = z;
    head[x] = tot;
}
int v[MAXN];
unsigned char flag;
bool ans, ans1, ans2, cnt1, cnt2;
inline bool dfs(int x, int c) {
    v[x] = c;
    if (c == 1)
        cnt1 ^= 1;
    else
        cnt2 ^= 1;
    for (register int i = head[x]; i; i = e[i].nxt) {
        register int y = e[i].ver;
        if (!v[y]) {
            if (!dfs(y, e[i].edge ? 3 - c : c))
                return 0;
        } else if ((e[i].edge && v[x] == v[y]) || (!e[i].edge && v[x] != v[y]))
            return 0;
    }
    return 1;
}
inline void main() {
    memset(head, 0, sizeof(head));
    memset(v, 0, sizeof(v));
    tot = 0;
    ans1 = ans2 = 0;
    poread(h), poread(w);
    for (register int i = 1; i <= h; ++i) {
        for (register int j = 1; j <= w; ++j) {
            char c = charget();
            if (c == 'x')
                add(i, j + h, 1), add(j + h, i, 1);
            else if (c == 'o')
                add(i, j + h, 0), add(j + h, i, 0);
        }
    }
    for (register int i = 1; i <= h + w; ++i) {
        if (v[i])
            continue;
        cnt1 = cnt2 = 0;
        if (!dfs(i, 1)) {
            return (void)puts((h + w & 1) ? "1" : "0");
        }
        if (cnt1 != cnt2)
            ans1 ^= 1;
        else
            ans2 ^= cnt1;
    }
    return (void)puts(ans2 | ans1 ? "3" : "2");
}
}  // namespace ddd
signed main() {
    int T;
    poread(T);
    while (T--) ddd::main();
}

猜你喜欢

转载自www.cnblogs.com/Shiina-Rikka/p/11736153.html
今日推荐