分类 默认分类 下的文章

A

题意:给定$n$个单词组成的集合,每次操作从这个集合中选择若干个单词(无序,可重复)取其首字母拼成新串,新串放回集合中,重复该过程若干次,判断给定的一组串能否由该过程生成

这道题只需关注每个单词的首字母。因为每次操作我们取若干已有串的首字母拼成新串,新串加入集合后,后续操作也只取它的首字母。因此整个过程中所有能作为首字母出现的字符,都来自于初始 $n$ 个单词的首字母,不会产生任何新的字母。

一个目标串能被生成,当且仅当它的每一个字符都在初始首字母集合中出现过.因为只要字符存在,就可以在一次操作中直接选择对应数量的初始单词(或已生成的单字符串)拼出该串(因为顺序任意),不需要额外的依赖关系。

所有可能生成的目标串就是能生成的最大目标串(把所有的首字母都用上)的每个排列组合的任意子集

所以只需读入初始单词,将其首字母存入集合,然后检查每个目标串的每个字符是否都在集合中,若全部满足则输出 YES,否则 NO。时间复杂度 $O((n+m)L)$。

#include <bits/stdc++.h>
using namespace std;
#define int long long
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int T;
    cin >> T;
    while (T--)
    {
        int n, m;
        cin >> n >> m;
        set<char> st;
        for (int i = 0; i < n; i++)
        {
            string w;
            cin >> w;
            st.insert(toupper(w[0]));
        }
        bool ok = true;
        for (int i = 0; i < m; i++)
        {
            string a;
            cin >> a;
            for (char c : a)
            {
                if (st.find(c) == st.end())
                {
                    ok = false;
                }
            }
        }
        cout << (ok ? "YES" : "NO") << '\n';
    }
    return 0;
}

B

题意:两个巨人 Bea 和 Ver 各自有一排山,高度从左到右非递增。Bea 的山从左往右编号,Ver 的山从右往左编号(即两人面对面站着)。初始两人都站在各自第 1 座山上。每回合当前玩家选择对方当前站着的山,将其高度减 1。然后检查自己面前的山(编号 +1)是否比当前站着的山更高,如果是则跳到那座山;如果自己站在高度为 0 的山上且前面没有山了,则认输。Bea 先手,问谁赢。

考虑每个人能撑的回合数只取决于第一座山的高度加上后面山的数量。因为山的高度非递增,且每回合对手只能攻击你当前站着的山,把它减1。当这座山降到0时,如果你后面还有山,你就会跳到下一座山。(因为当前山的高度为$0$,下一座的高度一定大雨$0$)每座山被攻击的次数等于它的高度,而跳到下一座山需要额外的1次“移动”,所以总可承受攻击次数为 $a_1 + n - 1$。同理 Ver 为 $b_1 + m - 1$。Bea 先手,若 Bea 的可承受次数大于等于 Ver 的,则 Bea 赢,否则 Ver 赢。

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 100005;
int n, m;
long long a[N], b[N];
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int T;
    cin >> T;
    while (T--)
    {
        cin >> n >> m;
        for (int i = 1; i <= n; i++)
            cin >> a[i];
        for (int i = 1; i <= m; i++)
            cin >> b[i];
        int nb = a[1] + n - 1;
        int nv = b[1] + m - 1;
        cout << (nv <= nb ? 1 : 2) << '\n';
    }
    return 0;
}

C

题意:给定一棵 $n$ 个节点的有根,其中有 $m$ 个特殊节点。你需要选择若干个节点,使得每个特殊节点到根的路径上至少有一个被选中的节点,且选中的节点不能是特殊节点本身。要求选中的节点数量尽可能少,并在数量最少的前提下输出这些节点的编号。

这显然是一道贪心题。从叶子向根处理,记录每个子树中是否包含特殊节点 has[u],以及子树中是否已被覆盖 cov[u]。

对于节点 $u$,先处理完所有子节点。如果 $u$ 本身是特殊节点,它必须被覆盖,但不能选 $u$ 本身,所以只能从子节点中选取那些已被覆盖的子节点来覆盖 $u$。

如果 $u$ 不是特殊节点,统计有多少个子节点已被覆盖:若没有,且 $u$ 的子树中有特殊节点,则 $u$ 必须被选中;若只有一个,则 $u$ 不需要额外选,直接继承该覆盖;若有多个,则选 $u$ 可以合并这些覆盖,减少总选中数量,所以选 $u$ 并替换掉这些子节点。

由于从叶子节点向上,所以能够确保每个子树应用的都是最优策略,每一步都是当前的最优策略(注意这里为什么不说继承的都是最优策略呢?因为如果我们发现有多个特殊节点在一个子树里会合并,不合并时继承下去的显然不是最优的,但是我们在进行到那个子树的根之前显然是不知道不是最优的,当时最优的选择就只是这个,所以只是当前步骤是最优策略),所以贪心是最优策略。

最后输出选中的节点编号即可。

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 100005;
int n, m;
int p[N];
bool vis[N], has[N], cov[N];
int out[N], tot;
int head[N], to[N], nxt[N], cnt;
void add(int u, int v)
{
    to[++cnt] = v;
    nxt[cnt] = head[u];
    head[u] = cnt;
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int T;
    cin >> T;
    while (T--)
    {
        cin >> n;
        for (int i = 1; i <= n; i++)
        {
            head[i] = 0;
            vis[i] = 0;
            has[i] = 0;
            cov[i] = 0;
        }
        cnt = 0;
        for (int i = 2; i <= n; i++)
        {
            cin >> p[i];
            add(p[i], i);
        }
        cin >> m;
        for (int i = 1; i <= m; i++)
        {
            int x;
            cin >> x;
            vis[x] = 1;
        }
        tot = 0;
        for (int u = n; u >= 1; u--)
        {
            has[u] = has[u] || vis[u];
            if (!has[u])
                continue;
            int sz = 0;
            int gm[N];
            for (int i = head[u]; i; i = nxt[i])
            {
                int v = to[i];
                if (cov[v])
                    gm[++sz] = v;
            }
            if (vis[u])
            {
                for (int i = 1; i <= sz; i++)
                    out[++tot] = gm[i];
                cov[u] = 1;
            }
            else
            {
                if (sz == 0)
                {
                    cov[u] = 0;
                }
                else if (sz == 1)
                {
                    cov[u] = 1;
                }
                else
                {
                    for (int i = 1; i < sz; i++)
                        out[++tot] = gm[i];
                    cov[u] = 1;
                }
            }
            if (u > 1)
            {
                has[p[u]] = has[p[u]] || has[u];
            }
        }
        sort(out + 1, out + tot + 1);
        cout << tot;
        for (int i = 1; i <= tot; i++)
            cout << ' ' << out[i];
        cout << '\n';
    }
    return 0;
}

D

题意:

难度排序(个人想法):E>D>F>C>A>B

A

题意:给定三个非负整数 $a,b,c$,每次操作选择其中一个数替换为另外两个数的和,求经过任意次操作后最大值与最小值之差的最小可能值。

设排序后 $x\le y\le z$。关键观察是当 $z>x+y$ 时,只有把 $z$ 替换为 $x+y$ 能缩小范围。因为选 $x$ 替换为 $y+z$ 或选 $y$ 替换为 $x+z$ 都会使新范围变为 $z$,而原范围为 $z-x$,由于 $z>x+y\ge x$ 可知 $z>z-x$,范围变大;选 $z$ 替换为 $x+y$ 后新范围为 $y$(若 $x\le y$),而 $y<z-x$,范围变小。因此贪心选择最大数替换为另外两数之和是最优的。

当 $z\le x+y$ 时,选 $z$ 替换为 $x+y$ 会得到新范围 $\ge z-x$,选 $y$ 或 $x$ 分别替换都会使范围变为 $z$,均不会更小,因此当前状态已最优。

每次操作后最大值严格减小,算法必然终止。所以反复执行 $z=x+y$ 并排序,直到 $z\le x+y$,输出 $z-x$ 即可,时间复杂度 $O(\log \max)$。

B

题意:给定一个长度为 $n$ 的字符串 $s$,包含 0、1 或 ?,将每个 ? 替换为 0 或 1,使得相邻两个字符之和 $s_i+s_{i+1}$ 形成的 $n-1$ 个多米诺骨牌重量两两不同,求替换方案数对 $998244353$ 取模。

这道题的核心约束是相邻两个多米诺骨牌重量不能相同。第 $i$ 个骨牌的重量为 $s_i + s_{i+1}$,第 $i+1$ 个骨牌的重量为 $s_{i+1} + s_{i+2}$,两者不等即 $s_i + s_{i+1} \ne s_{i+1} + s_{i+2}$,两边同时消项 $s_{i+1}$得 $s_i \ne s_{i+2}$。

这意味着奇数位置上的字符之间必须交替,即 $s_1 \ne s_3 \ne s_5 \ne \cdots$,偶数位置上的字符之间也必须交替,即 $s_2 \ne s_4 \ne s_6 \ne \cdots$,而奇偶两条链之间没有任何限制,彼此完全独立。

于是将原字符串按奇偶下标拆分成两条独立的链,分别计算每条链中相邻字符不能相同的填 ? 方案数,最后将两条链的方案数相乘即为答案。

对于单条链的 DP,状态定义 $dp0$ 表示当前位填 $0$ 的方案数,$dp1$ 表示当前位填 $1$ 的方案数。初始化根据第一位能否填 $0$ 或 $1$ 分别赋 $1$。转移时若当前位可以是 $0$,则它只能从前一位是 $1$ 的状态转移过来,即 $ndp0 = dp1$;若当前位可以是 $1$,则只能从前一位是 $0$ 的状态转移过来,即 $ndp1 = dp0$。最后该链的方案数为 $dp0+dp1$。

两条链独立计算后相乘,每一步对 $998244353$ 取模即可。

C

题意: $2n$ 个人围成一圈,奇数编号属红队,偶数编号属蓝队。每轮持有土豆者可以选择原地不动或顺时针传给下一个空位,若下一位置已有土豆则必须原地不动,游戏持续 $k$ 轮,结束后持有土豆者被淘汰,其所属队伍给对方队伍贡献 $1$ 分,双方均采取最优策略。

分析可知,若有人在中间轮次提前传出土豆,由于相邻位置奇偶性必然改变,土豆会落入对方阵营手中,对手可直接原地不动按住直到结束,反而导致己方损失,因此在最优策略下前 $k-1$ 轮不会有人移动,所有土豆保留到最后一轮才进行单次移动。

所以无论 $k$ 多大,结果等价于只模拟一轮:每个土豆若当前位为 $1$ 且顺时针下一位置为空,则移动到下一位置,注意用新字符串记录避免连续移动。

模拟完后,土豆落在奇数编号(红队)则蓝队得分,落在偶数编号(蓝队)则红队得分,输出两队分数即可。

D

题意:给出一个长度为 $n$ 的二进制字符串 $s$,每次操作选择两个索引 $l\le r$ 且 $s_l=s_r$,将子串 $s[l..r]$ 翻转,可以操作任意次,求能得到的不同的二进制字符串数量,对 $998244353$ 取模。

考虑定义数组 $d$,其中 $d_i=1$ 表示 $s_i\ne s_{i+1}$,否则为 $0$。操作等价于在 $d$ 上选择一段包含偶数个 $1$ 的子段并将其翻转。设 $d$ 中 $1$ 的个数为 $k$,这些 $1$ 将 $d$ 切分成 $k+1$ 个由 $0$ 组成的连续块,长度依次为 $x_0,x_1,\ldots,x_k$。翻转包含偶数个 $1$ 的子段不会改变偶数下标块之和 $E=x_0+x_2+\cdots$ 与奇数下标块之和 $O=x_1+x_3+\cdots$,这是操作的不变量。

证明:翻转 $d$ 的一个子段等价于将该子段内的所有 $0$ 块顺序倒置,但保持块与 $1$ 的相对位置不变。考虑一个包含偶数个 $1$ 的子段,它跨越了偶数个 $1$,即从某个 $1$ 之前开始到某个 $1$ 之后结束,跨越的块下标范围是连续的偶数个 $1$ 所夹的块。翻转后,原本在偶数位置的块会移动到奇数位置,奇数位置的块会移动到偶数位置,但偶数位置的块数量与奇数位置的块数量在这一段内是相等的,因此段内 $E$ 和 $O$ 各自保持不变,段外不受影响,所以全局 $E$ 和 $O$ 守恒。

偶数块共有 $c_E=\lfloor k/2\rfloor+1$ 个,奇数块共有 $c_O=\lceil k/2\rceil$ 个,同奇偶性的块之间 $0$ 可以自由分配,不同奇偶性之间不能流通,于是用隔板法分别计算方案数再相乘,答案为 $\binom{E+c_E-1}{c_E-1}\times\binom{O+c_O-1}{c_O-1}\bmod{998244353}$。

E

通信好题啊!

题意:给你一张只有黑块和白块组成的图和目标块$z$,操作员1先交换两个方块,然后交到操作员2手上,操作员2可以对这张图进行平移,旋转,翻转和反色四个操作,操作是随机的没有规律,进行完操作后把图交到操作员3手上,要求操作员3输出相对于最终变换(操作员2进行完变换的图)的$z$的坐标。

我们发现操作员1想要传递消息给操作员3,唯一能做的就是交换两个方块的颜色。所以问题的核心是如何通过一次交换,在图片中编码目标块的位置信息,使得无论操作员2如何变换图片,操作员3都能从最终图片中解码出目标块的坐标。

答案出乎人意料:利用黑块的横坐标和纵坐标的平均值来传递$z$的信息。操作员1通过交换一个黑块和一个白块,使图片中黑块横坐标的平均值和纵坐标的平均值分别等于目标块的横坐标和纵坐标。那怎么证明交换两个块之后一定可以使所有黑块横纵坐标的平均值等于$z$呢?证明如下:

设当前黑块数量为 $w$,所有黑块的横纵坐标和分别为 $S_x=\sum x_i$,$S_y=\sum y_i$。目标点为 $(z_x,z_y)$。

假设不存在这样的交换。也就是说,对于每一个黑块 $(x_1,y_1)$,按照公式算出的目标位置 $(x_2,y_2)$ 都不是白块,全部都是黑块。

对于任意黑块 $(x_1,y_1)$,满足交换后平均值等于目标点的位置为:

$$ x_2 = x_1 + C_x,\quad y_2 = y_1 + C_y $$

其中 $C_x = w z_x - S_x$,$C_y = w z_y - S_y$ 是常数。所有坐标在模 $n$ 下运算(因为图片是一个环面,左右边界相连、上下边界相连,坐标超出 $1$ 到 $n$ 范围时会自动循环回来)。

定义平移映射 $T(x,y)=(x+C_x,y+C_y)$。反证假设等价于:$T$ 把每一个黑块都映射到黑块。

因为 $T$ 是双射,且黑块集合有限,所以 $T(\text{黑块集合}) = \text{黑块集合}$,即黑块集合在平移 $T$ 下不变。

这个时候矛盾就出现了!如果 $T$ 是非零平移($C_x\neq0$ 或 $C_y\neq0$),它在环面上把每个点都移到了另一个位置,不可能把一个有限集合映射到自身。因此 $T$ 必须是零平移,即 $C_x=C_y=0$。

但 $C_x=C_y=0$ 意味着 $S_x=w z_x$,$S_y=w z_y$,即当前黑块平均值已经等于目标点。此时操作员1不需要交换,选同一个格子即可完成编码,这与反证假设“不存在这样的交换”矛盾(因为选同一个格子也算一种交换,且已经达到目标)。

所以反证假设不成立。因此必然存在一个黑块 $(x_1,y_1)$,使得对应的 $(x_2,y_2)$ 是白块。交换它们后,黑块平均值恰好等于目标点 $(z_x,z_y)$。证毕。

我们现在已经有了结论1,即我们一定可以找到一个交换,使得我们可以使用所有黑块横纵坐标的平均值表达$z$。那我们如何确保$z$的表达,即所有黑块横纵坐标的平均值在形形色色的操作员2的变换中不变呢?(这里的不变是指对于操作员2操作完的图而言)以下是证明:

设原始图片中所有黑块坐标平均值为 $G=(\bar r,\bar c)$,目标点为 $z=(z_r,z_c)$,且操作员1通过交换使 $G=z$。

平移 $(d_r,d_c)$:每个黑块坐标变为 $(r_i+d_r,c_i+d_c)$,平均值变为 $G+(d_r,d_c)$,目标点也变为 $z+(d_r,d_c)$,两者仍相等。

顺时针旋转 $90^\circ$:每个黑块坐标变为 $(c_i,n+1-r_i)$,平均值变为 $(\bar c,n+1-\bar r)$。目标点也经历相同变换 $(z_c,n+1-z_r)$,两者仍相等。

沿竖直轴翻转:每个黑块坐标变为 $(r_i,n+1-c_i)$,平均值变为 $(\bar r,n+1-\bar c)$。目标点也变为 $(z_r,n+1-z_c)$,两者仍相等。

因此,无论操作员2如何平移、旋转、翻转图片,黑块坐标平均值始终等于目标块在变换后图片中的坐标。操作员3只需计算最终图片的黑块平均值,即可直接得到目标块的位置。

然后是复杂一点的反色,这个时候我们就要用到那个尘封已久的互质条件了。

设反色前黑块数量为 $c$,横坐标之和为 $\sum x$。反色后黑块数量变为 $c' = n^2 - c$,横坐标之和变为 $\sum x' = T - \sum x$,其中 $T = \frac{n(n+1)}{2}\times n$ 是所有格子的横坐标总和。

操作员3计算的是当前图片黑块坐标平均值,即 $S_x \cdot w^{-1} \bmod n$,其中 $S_x$ 是当前黑块横坐标之和,$w$ 是当前黑块数量,$w^{-1}$ 是 $w$ 在模 $n$ 下的逆元(满足 $w \cdot w^{-1} \equiv 1 \pmod n$)。因为 $\gcd(n,w)=1$,逆元一定存在。

如果发生了反色,则 $S_x = T - \sum x$,$w = n^2 - c$。

因为 $T \equiv 0 \pmod n$,所以 $S_x \equiv -\sum x \pmod n$。又因为 $w = n^2 - c \equiv -c \pmod n$,所以:

$$ S_x \cdot w^{-1} \equiv (-\sum x) \cdot (-c)^{-1} \equiv \sum x \cdot c^{-1} \pmod n $$

而 $\sum x \cdot c^{-1}$ 正是反色前的平均值,操作员1已经通过交换使之等于目标点 $z$。所以操作员3的计算结果 $S_x \cdot w^{-1} \bmod n$ 就等于目标点的横坐标。纵坐标同理。

所以操作员3不需要管反不反色,统一算 $S_x \cdot w^{-1} \bmod n$ 和 $S_y \cdot w^{-1} \bmod n$ 即可。证毕。

结合一下以上的过程,最终的步骤大概就是这样的:

操作员1(编码):

  1. 统计当前图片中黑块数量 $w$,以及所有黑块的横纵坐标和 $(S_x,S_y)$。
  2. 读入目标点 $(z_x,z_y)$。
  3. 如果当前平均值已经等于目标点(即 $S_x=w\cdot z_x$ 且 $S_y=w\cdot z_y$),选择任意一个格子与自身交换(不改变图片)。
  4. 否则,枚举每一个黑块 $(x_1,y_1)$:
    计算 $x_2 = (x_1 + w\cdot z_x - S_x) \bmod n$,$y_2 = (y_1 + w\cdot z_y - S_y) \bmod n$,如果 $(x_2,y_2)$ 是白块,交换 $(x_1,y_1)$ 和 $(x_2,y_2)$,结束。
  5. 输出交换的两个格子坐标。

操作员3(解码):

  1. 统计最终图片中黑块数量 $w$,以及所有黑块的横纵坐标和 $(S_x,S_y)$。
  2. 求 $w$ 在模 $n$ 下的逆元 $w^{-1}$(满足 $w\cdot w^{-1}\equiv 1\pmod n$)。因为 $\gcd(n,w)=1$,逆元一定存在。
  3. 计算 $\bar r = S_x \cdot w^{-1} \bmod n$,$\bar c = S_y \cdot w^{-1} \bmod n$。
  4. 输出 $(\bar r+1,\bar c+1)$(因为模运算结果是 $0$ 到 $n-1$,而坐标从 $1$ 开始编号,所以需要加 $1$)。

F

这道题的操作是选择一个数执行向下取整,其余数执行向上取整。数学本质是:向下取整对应二进制右移一位,向上取整对应先加1再右移一位。

一个数 $a_i$ 能在 $T$ 秒内归零,当且仅当它能被分配给它的若干个不重复的 $2$ 的幂次覆盖,即 $a_i \le \sum_{j \in S_i} 2^j$,其中 $S_i$ 是分配给该数的时间槽集合,所有 $S_i$ 互不相交且是 $\{0,1,\ldots,T-1\}$ 的子集。

于是问题转化为从总价值为 $2^{T-1},2^{T-2},\ldots,2^0$ 的时间槽中分出互不相交的集合去满足 $n$ 个数的需求。由于 $T$ 越大越容易满足条件,具有单调性,因此二分答案 $T$,下界为 $1$,上界约为 $n+35$。

验证时,若 $T>30$,则会产生 $T-30$ 个大于 $2^{30}$ 的超级幂次,每个超级幂次可无条件直接消灭任意一个数,先用它们消灭数组中最大的那些数;剩余元素放入大根堆,从大到小枚举 $2^{29}$ 到 $2^0$:若当前幂次大于等于堆顶,则直接清零弹出;若小于堆顶,则用其削减,将残余值 $x-2^j$ 压回堆中。若最后堆为空则可行,否则不可行。单次验证用大根堆贪心即可。