旅行传送门

A. Median Maximization

题意:给你两个正整数 \(n\)\(s\) 。求由 \(n\) 个非负整数组成的总和为 \(s\) 的序列的最大中位数。

题目分析:显然,令前 $ \frac{n+1}{2}-1$ 个数为 \(0\) 时序列有最大中位数。

AC代码

#include <bits/stdc++.h>
#define rep(i, x, y) for (register int i = (x); i <= (y); i++)
#define down(i, x, y) for (register int i = (x); i >= (y); i--)
using ll = long long;

char buf[1 << 23], *p1 = buf, *p2 = buf, obuf[1 << 23], *O = obuf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
inline ll read()
{
    ll x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}

ll solve()
{
    ll n = read(), s = read();
    return n & 1 ? s / ceil(1.0 * n / 2) : s / (ceil(1.0 * n / 2) + 1);
}

int main(int argc, char const *argv[])
{
    int T = read();
    while (T--)
        printf("%lld\n", solve());
    return 0;
}

B. MIN-MEX Cut

题意:设二进制字符串的 \(MEX\) 为字符串中未出现的 \(0\)\(1\)\(2\) 中最小的数字。

现在给你一个字符串 \(s\) ,你可以将 \(s\) 切成任意几份(分成几个子串),求所有子串片段的最小 \(MEX\) 总和。

题目分析:要使得 \(MEX\) 总和最小,显然要将连续的 \(0\) 切分出来。另外要明确的是 \(ans \leq 2\) ,因此比较连续段 \(0\) 的段数和 \(2\) 的大小即可。

AC代码

#include <bits/stdc++.h>
#define rep(i, x, y) for (register int i = (x); i <= (y); i++)
#define down(i, x, y) for (register int i = (x); i >= (y); i--)
#define IOS                      \
    ios::sync_with_stdio(false); \
    cin.tie(nullptr);            \
    cout.tie(nullptr);
using namespace std;

int solve()
{
    string s;
    cin >> s;
    int len = s.length();
    int i = 0, res = 0;
    while (i < len)
    {
        while (s[i] - '0' == 0)
        {
            ++i;
            if (s[i] - '0' == 1)
            {
                ++res;
                break;
            }
        }
        ++i;
    }
    if (s[len - 1] - '0' == 0)
        ++res;
    return std::min(res, 2);
}

int main(int argc, char const *argv[])
{
    int T;
    cin >> T;
    while (T--)
        printf("%d\n", solve());
    return 0;
}

C. MAX-MEX Cut

题意:给你一个 \(2 \times n\)\(01\) 矩阵, 你可以将该矩阵切成任意几份(分成几个 \(2 \times m\) 的子矩阵),求所有子矩阵的最大 \(MEX\) 总和。

题目分析:我们不妨枚举下 \(2 \times 2\)\(01\) 矩阵的所有情况,\({\begin{bmatrix}1&0\\0&1\end{bmatrix}}\)\({\begin{bmatrix}1&0\\0&0\end{bmatrix}}\)\({\begin{bmatrix}1&1\\0&1\end{bmatrix}}\)\({\begin{bmatrix}0&0\\1&0\end{bmatrix}}\)\({\begin{bmatrix}0&1\\1&1\end{bmatrix}}\)\({\begin{bmatrix}0&1\\0&1\end{bmatrix}}\) ,不难发现,\({\begin{bmatrix}1\\1\end{bmatrix}}\) 本身对答案是没有贡献的,但它与 \({\begin{bmatrix}0\\0\end{bmatrix}}\) 组合时,却能使 \({\begin{bmatrix}0\\0\end{bmatrix}}\) 的贡献 \(+1\) ,而 \({\begin{bmatrix}1\\0\end{bmatrix}}\)\({\begin{bmatrix}0\\1\end{bmatrix}}\))的贡献本身已经是 \(2\) 了,与其它的 \(1 \times 2\) 矩阵组合时也不会产生多的贡献。那么我们的思路已经很明确了,先找出主矩阵中的所有 \({\begin{bmatrix}1\\1\end{bmatrix}}\) 子矩阵,与它旁边空闲的 \({\begin{bmatrix}0\\0\end{bmatrix}}\) 组合,最后再扫一遍将未组合的 \(1 \times 2\) 矩阵切分出来加上贡献即是最终答案。

AC代码

#include <bits/stdc++.h>
#define rep(i, x, y) for (register int i = (x); i <= (y); i++)
#define down(i, x, y) for (register int i = (x); i >= (y); i--)
#define IOS                      \
    ios::sync_with_stdio(false); \
    cin.tie(nullptr);            \
    cout.tie(nullptr);
using namespace std;

int solve()
{
    int n, res = 0;
    string s1, s2;
    cin >> n >> s1 >> s2;
    std::vector<int> vis(n);
    rep(i, 0, n - 1)
    {
        if (s1[i] == '1' && s2[i] == '1')
            if (i > 0 && s1[i - 1] == '0' && s2[i - 1] == '0' && !vis[i - 1] && !vis[i])
                res += 2, vis[i - 1] = vis[i] = 1;
            else if (i < n && s1[i + 1] == '0' && s2[i + 1] == '0' && !vis[i] && !vis[i + 1])
                res += 2, vis[i] = vis[i + 1] = 1;
    }
    rep(i, 0, n - 1)
    {
        if (s1[i] != s2[i])
            res += 2, vis[i] = 1;
        else if (s1[i] == '0' && s2[i] == '0' && !vis[i])
            ++res, vis[i] = 1;
    }
    return res;
}

int main(int argc, char const *argv[])
{
    int T;
    cin >> T;
    while (T--)
        printf("%d\n", solve());
    return 0;
}

D1. Seating Arrangements (easy version)

题意:有 \(n \times m\) 个人和 \(n \times m\) 个座位,座位编号如题面所示。每个人的视力对应一个值 \(a_i\)\(a_i\)越小的人座位越靠前。从第 \(1\) 个人至 第 \(n \times m\) 个人依次就座,每个人就座时的代价为该行经过的人数,求如何安排座位可使代价最小化。

题目分析: 显然,对某一行来说,若同一个数值连续出现,那么他们之间不会产生任何代价(把编号较小的放进靠后的位置)。此外,数值大的在数值小的之前出现,也不会产生任何代价(数值大的先坐进靠后的位置去了)。那么会对答案产生贡献的不就一种情况了吗?数值小的在数值大的之前!这是什么?正序对?树状数组走起。

需要注意: \(a_i\)很大,离散一下。

AC代码

#include <bits/stdc++.h>
#define rep(i, x, y) for (register int i = (x); i <= (y); i++)
#define down(i, x, y) for (register int i = (x); i >= (y); i--)
#define full(x, y) memset(x, y, sizeof(x))
#define lowbit(x) ((x) & (-x))
const int maxn = 305;

char buf[1 << 23], *p1 = buf, *p2 = buf, obuf[1 << 23], *O = obuf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}

int n, m, a[maxn], b[maxn], tree[maxn];

void add(int x, int k)
{
    for (; x <= m; x += lowbit(x))
        tree[x] += k;
}

int ask(int x)
{
    int res = 0;
    for (; x; x -= lowbit(x))
        res += tree[x];
    return res;
}

int solve()
{
    full(tree, 0);
    int res = 0;
    n = read(), m = read();
    rep(i, 1, m) a[i] = b[i] = read();
    std::sort(b + 1, b + m + 1);
    int nn = std::unique(b + 1, b + m + 1) - b - 1;
    rep(i, 1, m) a[i] = std::lower_bound(b + 1, b + nn + 1, a[i]) - b;
    rep(i, 1, m) add(a[i], 1), res += ask(a[i] - 1);
    return res;
}

int main(int argc, char const *argv[])
{
    int T = read();
    while (T--)
        printf("%d\n", solve());
    return 0;
}

D2. Seating Arrangements (hard version)

题目分析:与 \(easy\) \(version\) 不同的是现在有多行了,基本思路还是树状数组求正序对,一开始想的是先按数值排个序,然后按顺序每行选出 \(m\) 个人,再对这 \(m\) 个人按编号排序,用D1的做法求他们对答案的贡献,可惜超时了 ┑(﹀_﹀)┍

后来去luogu逛了一圈发现有julao也用的是树状数组的思路,但julao的处理方式明显比我更优,这里开个旅行传送门,在此不过多赘述了。

AC代码

#include <bits/stdc++.h>
#define rep(i, x, y) for (register int i = (x); i <= (y); i++)
#define down(i, x, y) for (register int i = (x); i >= (y); i--)
#define pii std::pair<int, int>
#define full(x, y) memset(x, y, sizeof(x))
#define lowbit(x) ((x) & (-x))
const int maxn = 1e5 + 5;

char buf[1 << 23], *p1 = buf, *p2 = buf, obuf[1 << 23], *O = obuf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}

int n, m, cnt;
pii a[maxn], b[maxn];
int c[maxn], tree[305][maxn];
std::map<int, int> mp;

void add(int x, int y, int k)
{
    for (; x <= cnt; x += lowbit(x))
        tree[y][x] += k;
}

int ask(int x, int y)
{
    int res = 0;
    for (; x; x -= lowbit(x))
        res += tree[y][x];
    return res;
}

inline bool cmp(pii x, pii y) { return x.second ^ y.second ? x.second < y.second : x.first < y.first; }

int solve()
{
    int res = 0;
    n = read(), m = read();
    rep(i, 1, n * m) a[i].second = b[i].second = c[i] = read(), b[i].first = i;
    std::sort(b + 1, b + n * m + 1, cmp);
    rep(i, 1, n * m) a[b[i].first].first = i;
    std::sort(c + 1, c + n * m + 1);
    cnt = std::unique(c + 1, c + n * m + 1) - c - 1;
    rep(i, 1, n * m) a[i].second = std::lower_bound(c + 1, c + cnt + 1, a[i].second) - c;
    rep(i, 1, n) rep(j, 1, cnt) tree[i][j] = 0;
    rep(i, 1, n * m)
    {
        int id = (a[i].first - 1) / m + 1;
        res += ask(a[i].second - 1, id);
        add(a[i].second, id, 1);
    }
    return res;
}

int main(int argc, char const *argv[])
{
    int T = read();
    while (T--)
        printf("%d\n", solve());
    return 0;
}

E. Buds Re-hanging

题意:给你一棵树,树的芽满足以下条件:

  • 非根非叶子节点
  • 所有子节点均为叶子节点

你可以剪掉一棵芽(删去芽与其父节点的连边)并放在其他任意节点上,该操作可以进行任意次,现在要你最小化叶子节点数目,并求最小值是多少。

题目分析:结论题,首先将整棵树尽可能多地拆成芽并放在根节点上形成一棵深度 \(\leq 3\) 的树,因为每个带有 \(x\) 个叶子节点的芽在嫁接到另一节点上后,对答案的贡献是 \(x-1\) ,所以保持其中一个芽不动,其它芽顺着它连成一条链即是最优解。

图是从luogu偷的,但画的着实好,实在忍不住。

Codeforces Global Round 16 题解_#define

为了观感更好,我在原图连成链的时候把嫁接的地方换成虚线了。

Codeforces Global Round 16 题解_c++_02

AC代码

#include <bits/stdc++.h>
#define rep(i, x, y) for (register int i = (x); i <= (y); i++)
#define down(i, x, y) for (register int i = (x); i >= (y); i--)
const int maxn = 2e5 + 5;

char buf[1 << 23], *p1 = buf, *p2 = buf, obuf[1 << 23], *O = obuf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 21, stdin), p1 == p2) ? EOF : *p1++)
inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch))
    {
        if (ch == '-')
            f = -1;
        ch = getchar();
    }
    while (isdigit(ch))
    {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}

int n, ans;
std::vector<int> e[maxn];

void init()
{
    //其中一个芽不动,其对答案的贡献就为其叶节点数x而非x-1
    ans = 1;
    rep(i, 1, n) e[i].clear();
}

int dfs(int u, int f)
{
    int cnt = 0;
    for (auto v : e[u])
    {
        if (v == f)
            continue;
        cnt += dfs(v, u);
    }
    if (cnt)
        ans += cnt - 1;
    return cnt ? 0 : 1;
}

int main(int argc, char const *argv[])
{
    int T = read();
    while (T--)
    {
        n = read();
        init();
        rep(i, 1, n - 1)
        {
            int u = read(), v = read();
            e[u].push_back(v);
            e[v].push_back(u);
        }
        dfs(1, 0);
        printf("%d\n", ans);
    }
    return 0;
}