1496A – Split it!

题意

给定 Sk,要求把 S 拆分为

a_1 + a_2 + \cdots + a_k + a_{k + 1} + R(a_k) + R(a_{k – 1}) + \cdots + R(a_i)

的形式,其中 R(a) 表示 A 的反串,例如 R(abcd) = dcba

题解

注意到这个其实是类似回文的形式,但是需要读清楚题意,发现中间还有一个 a_{k + 1},所以整个串不一定是回文的。

数据范围 n\le 100,所以直接暴力做,从两端往中间扫,如果扫到的相等的字符数量大于或等于 k,说明有解。

注意判断 a_{k + 1} 是否存在,否则容易错,同时需要特判 k = 0 的情况,k = 0 必定有解。

代码如下:

#include <cstdio>
#include <cstring>
#define YES puts("YES")
#define NO puts("NO")

const int maxn = 105;
char s[maxn];
int n, k;

int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d %d", &n, &k);
        scanf("%s", s + 1);
        if (k == 0)
        {
            YES;
            continue;
        }
        int cnt = 0;
        for (int i = 1, j = n; i <= (n - 0.1) / 2; ++i, --j)
        {
            if (s[i] == s[j])
                ++cnt;
            else break;
        }
        if (cnt >= k)
            YES;
        else NO;
    }
    return 0;
}

1496B – Max and Mex

题意

给定一个多重集合 S,初始 S 中有 n 个互异的非负整数,定义 \max (S) 为该集合中的最大元素,定义 \mathrm{mex}(S) 为该集合中第一个未出现过的非负整数

进行 k 次操作,每次操作取出 a = \max(S)b = \mathrm{mex}(S),然后计算出 \left\lceil\frac{a + b}2\right\rceil 并插入回这个集合中。求 k 次操作之后集合中互异元素的数量。

题解

注意到 n\le 10^5, k\le 10^9,所以暴力模拟肯定是行不通的,同时这道题是 CF 的 div2 B,所以考虑寻找这题的结论。

首先,如果 \mathrm {mex}(S) 小于集合中最大的元素,则我们进行这个操作之后$\mathrm{mex}(S)$ 的值是不会发生改变的,举个例子:

S = \lbrace 1, 2, 3, 5, 6\rbrace,则 \mathrm{mex}(S) = 0,并且进行一次操作之后 S = \lbrace1, 2, 3, 5, 6\rbrace 不发生改变,所以如果 \mathrm{mex}(S) 在集合中不存在并且小于集合中的最大元素,进行一次操作之后就会陷入死循环,所以对于这种情况只需要查找 \left\lceil\frac{a + b}2\right\rceil 在原来的集合中是否存在。

但对于 S = \lbrace0, 1, 2, 3, 4\rbrace 这种情况,不难发现每进行一次操作之后 S 中互异元素的数量都会增加 1,所以直接输出 n + k 即可。

仍然需要注意 k = 0 的情况,需要进行特判。

代码如下,注意一开始所有的元素不一定是升序排列的,所以一开始需要进行排序。一开始寻找 \mathrm{mex}(S) 的方法也很简单:直接扫一遍即可。

#include <cstdio>
#include <cctype>
#include <algorithm>
#define FOR(i, a, b) for (int i = a; i <= b; ++i)

int read()
{
    int s = 0;
    char c = getchar();
    while (!isdigit(c))
        c = getchar();
    while (isdigit(c))
        s = 10 * s + c - '0', c = getchar();
    return s;
}

const int maxn = 1e5 + 5;
int a[maxn], n, k;

int rndup(int a, int b)
{
    int ret = a + b;
    if (ret & 1)
        ret = (ret >> 1) + 1;
    else ret >>= 1;
    return ret;
}

int main()
{
    int T = read();
    while (T--)
    {
        n = read(), k = read();
        FOR(i, 1, n) a[i] = read();
        if (k == 0)
        {
            printf("%d\n", n);
            continue;
        }
        std::sort(a + 1, a + n + 1);
        if (a[n] == n - 1)
        {
            printf("%d\n", k + n);
            continue;
        }
        int mex = 0;
        FOR(i, 1, n)
        {
            if (a[i] == mex)
                ++mex;
            else break;
        }
        //printf("mex%d\n", mex);
        if ((*std::lower_bound(a + 1, a + n + 1, rndup(a[n], mex))) == rndup(a[n], mex))
            printf("%d\n", n);
        else printf("%d\n", n + 1);
    }
    return 0;
}

1495A – Diamond Miner

题意

x 轴上有 n 个钻石矿,y 轴上有 n 个矿工,保证所有的矿工和钻石都不在原点。需要建立每个矿工与每个钻石矿的一一对应关系使得每对矿工和矿的距离之和最小。

题解

n 的数据范围为 10^5,暗示了我们要么是贪心要么是 O(n) 的 dp,先考虑两对矿工和钻石的情况:(由于正负其实不影响,所以我们把所有的钻石和矿的坐标都取绝对值,在正半轴上考虑问题。

两对点的情况无非就是交叉着连(蓝线)和平行着连(红线)。所以考虑哪个要大一些:

即比较 \sqrt{a^2 + d^2} + \sqrt{b^2 + c^2}\sqrt{a^2 + c^2} + \sqrt{b^2 + d^2} 的大小。

两边同时平方:

a^2 + b^2 + c^2 + d^2 + \sqrt{(a^2 + d^2)(b^2 + c^2)}

a^2 + b^2 + c^2 + d^ 2+ \sqrt{(a^2 + c^2)(b^2 + d^2)}

所以只需要考虑 (a + d)(b+ c)(a + c)(b + d) 的大小即可,化简之后不难发现

(a + d)(b + c)\ge(a + c)(b + d)

所以平行的连法一定是最优的。将所有的点取绝对值后排序然后依次累加答案就搞定了。

#include <cstdio>
#include <cctype>
#include <cmath>
#include <algorithm>
#define FOR(i, a, b) for (int i = a; i <= b; ++i)

int read()
{
    int s = 0;
    char c = getchar();
    while (!isdigit(c))
        c = getchar();
    while (isdigit(c))
        s = 10 * s + c - '0', c = getchar();
    return s;
}

typedef double db;

const int maxn = 1e5 + 5;
db x[maxn], y[maxn];
int n, cntx, cnty;

int main()
{
    int T = read();
    while (T--)
    {
        n = read();
        db ans = 0;
        cntx = cnty = 0;
        FOR(i, 1, n << 1)
        {
            int xx = read(), yy = read();
            if (!xx) y[++cnty] = yy;
            if (!yy) x[++cntx] = xx;
        }
        std::sort(x + 1, x + cntx + 1);
        std::sort(y + 1, y + cnty + 1);
        FOR(i, 1, n)
            ans += sqrt(x[i] * x[i] + y[i] * y[i]);
        printf("%.15lf\n", ans);
    }
    return 0;
}

1495B – Let’s Go Hiking

题意

给定一个 1-n 的排列 p,Qingshan 和 Daniel 在草稿纸上按照如下规则进行远足游戏。

首先 Qingshan 选定一个数 x 并告诉 Daniel,接下来 Daniel 选定另一个数 y1\le x, y\le nx\not=y。然后他们轮流进行游戏,Qingshan 为先手:

  • 如果轮到 Qingshan,则 Qingshan 只能把 x 移动到 x’,其中 |x’ – x| = 1,且 x’ \neq yp_x\gt p_{x’}
  • 如果轮到 Daniel,则 Daniel 只能把 y 移动到 y’,其中 |y’ – y| = 1,且 y’\neq xp_y\lt p_{y’}

如果轮到某一方时无路可走了,则另一方胜利。

假设双方都足够聪明,那么判断有多少个初始的 x 可以让 Qingshan 一定取胜。

题解

首先发掘这个游戏的性质:每个人只能往一个方向走,不能回头。注意到这个性质之后不难发现如果 Qingshan 要赢,那么 Daniel 必须被 Qingshan 卡死或者自己被卡死。

因为两方都足够聪明,所以我们把问题范围缩小到最长单调序列上面来,因为这样可以最大化双方的移动步数。

可以证明的是,Qingshan 一开始选的点在最长单调序列的顶端是他能赢的必要条件。至于为什么呢?如果 Qingshan 选的不是最长单调序列的顶端,那么 Daniel 就可以选最长单调序列的底端进行游戏,那么 Qingshan 肯定先被卡死。

并且如果有多条不相交的最长单调序列,Qingshan 必输。因为这样 Daniel 可以选择另外一条序列的底端,而 Qingshan 是先手,所以 Qingshan 肯定先被卡死

如果只有一条最长单调序列,还是 Qingshan 必输,因为 Qingshan 是先手,Daniel 选择的位置但凡与 Qingshan 的初始位置成的链长为偶数那么 Daniel 肯定可以卡死 Qingshan

剩下的两种情况就是同时有两条最长单调序列,要么成“V”形要么成山峰形。对于“V”形的情况还是 Qingshan 必输,因为 Daniel 可以往与 Qingshan 相反的方向跑走,那么当 Qingshan 最后到达谷底的时候就 GG 了。对于山峰形的情况,Qingshan 肯定是选峰顶,接下来分最长单调序列长度的奇偶性讨论:

奇数:如果 Daniel 选了坡底,那么 Qingshan 只要往 Daniel 的方向走那么就必然可以卡死 Daniel;如果不选坡底,那么 Qingshan 只要往另一个方向走就可以让 Daniel 先无路可走,所以此时答案为 1,Qingshan 唯一的必胜策略就是选择山顶。

偶数:类似上面的分析方法,不难发现要么 Daniel 卡死 Qingshan,要么 Qingshan 先无路可走,所以 Qingshan 必败

分析到这里我们就发现了答案为 1 当且仅当有两条长度为奇数的最长单调序列并且他们共享一个最高点,否则答案为 0O(n) 直接扫就可以过了。

#include <cstdio>
#include <cctype>
#include <set>
#define FOR(i, a, b) for (int i = a; i <= b; ++i)
#define DEC(i, a, b) for (int i = a; i >= b; --i)

int read()
{
    int s = 0;
    char c = getchar();
    while (!isdigit(c))
        c = getchar();
    while (isdigit(c))
        s = 10 * s + c - '0', c = getchar();
    return s;
}

inline int max(int a, int b) {return a > b ? a : b;}

const int maxn = 1e5 + 5;
int n, a[maxn], pre[maxn], suf[maxn], pos[maxn];

int main()
{
    n = read();
    FOR(i, 1, n)
        a[i] = read();
    FOR(i, 1, n)
        if (a[i] > a[i - 1])
            pre[i] = pre[i - 1] + 1;
        else pre[i] = 1;
    DEC(i, n, 1)
        if (a[i] > a[i + 1])
            suf[i] = suf[i + 1] + 1;
        else suf[i] = 1;
    int maxlen = 0, cnt = 0;
    bool flag = 0;
    FOR(i, 1, n)
        maxlen = max(max(maxlen, pre[i]), suf[i]);
    FOR(i, 1, n)
    {
        if (pre[i] == maxlen)
            ++cnt;
        if (suf[i] == maxlen)
            ++cnt;
        if (i + maxlen - 1 <= n && i - maxlen + 1 >= 1 && suf[i] == maxlen && pre[i] == maxlen)
            flag = 1;
    }
    if (cnt == 2 && flag)
    {
        printf("%d\n", maxlen & 1);
        return 0;
    }
    else puts("0");
    return 0;
}

1495C Garden of the Sun

题意

给定一个 n\times m 矩阵,里面含有字符 .X。保证所有 X 之间无公共点(即不联通),请将一部分 . 替换成 X 使得这些 X 形成一棵树(要求四联通,即两个 X 之间有一公共边,不能有环)

题解

一道构造题。

首先所有 X 之间无公共点是一个特别好的性质,这保证了下面填充列的方法的正确性。

具体地,考虑 3\mid m 的情况,不妨直接将 2,5,8,\cdots 列全部填成 X,不难发现这样隔两列填一列的方式是不会出现环的,然后再将 3, 6, 9, \cdots4,7,10,\cdots 等列构造成联通的就可以了,最简单的方法就是只考虑第一行和第二行,如果合法就直接连。

如果 3\not| m,那么只需要变一下,把 1, 4, 7,\cdots 填成 X,剩余操作类似。至于为什么填的列改变了则可以考虑画一下图自己模拟一下,会发现如果选 2, 5, 8,\cdots 的话在 m = 3k + 1 的时候会多出一个需要单独考虑的列,所以不如简化问题。

代码如下:

#include <cstdio>
#define FOR(i, a, b) for (int i = a; i <= b; ++i)

const int maxn = 505;
char s[maxn][maxn];
int n, m;

int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d %d", &n, &m);
        FOR(i, 1, n)
            scanf("%s", s[i] + 1);
        for (int j = 1 + (m % 3 == 0); j <= m;)
        {
            FOR(i, 1, n)
                s[i][j] = 'X';//直接全部赋值
            j += 3;
            if (j > m) break;
            int p = 2;//进行操作的行号
            if (n == 1 || (s[2][j - 1] != 'X' && s[2][j - 2] != 'X'))//如果第二不可以操作
                p = 1;//那就操作第一行
            s[p][j - 1] = s[p][j - 2] = 'X';//联通
        }
        FOR(i, 1, n)
            puts(s[i] + 1);
    }
    return 0;
}
最后修改日期: 2021年3月13日

作者

留言

Saaaaaaaaaaaiiiiiiiiiiikyo 

YangTY,yyds!

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。