AtCoder Beginner Contest 480 ABCDE
A - ADjacent
- 预估难度:
入门 - 标签:条件分支
题面
问题陈述
有一个 \(2\) 行 \(2\) 列的网格。
网格的每个格子中恰好写有 A、B、C、D 中的一个字符,且这 \(4\) 个格子中写出的字符互不相同。从上往下第 \(i\) 行、从左往右第 \(j\) 列的格子中写有 \(S_{i,j}\)。
如果写有 A 的格子与写有 D 的格子相邻(即它们共用一条边),则输出 Yes;否则输出 No。
约束
- \(S_{i,j}\) 是
A、B、C、D中的一个。 - 若 \((i_1,j_1)\neq (i_2,j_2)\),则 \(S_{i_1,j_1}\) 与 \(S_{i_2,j_2}\) 不同。
输入
输入从标准输入按以下格式给出:
\(S_{1,1}S_{1,2}\)
\(S_{2,1}S_{2,2}\)
输出
在一行中输出答案。
思路
直接判断可行,只是条件需要多写一些。
或者可以用 \((x_A, y_A), (x_D, y_D)\) 四个变量记录字符 A 和 D 所在行和列,那么在 \(2 \times 2\) 的网格中,“相邻”也就代表着两个字符在同一行或是同一列,也就是判断 \(x_A = x_D\) 或 \(y_A = y_D\)。
代码
void solve()
{
int ax, ay, dx, dy;
for(int i = 1; i <= 2; i++)
for(int j = 1; j <= 2; j++)
{
char c;
cin >> c;
if(c == 'A')
ax = i, ay = j;
if(c == 'D')
dx = i, dy = j;
}
if(ax == dx || ay == dy) // 同一行或者同一列
cout << "Yes";
else
cout << "No";
}
B - Invalid Vote
- 预估难度:
普及- - 标签:字符串、模拟
题面
问题陈述
\(N\) 名候选人 \(1,2,\dots,N\) 正在参加一场选举。每名候选人的名字均由小写英文字母组成,且互不相同。候选人 \(i\) 的名字为 \(S_i\)。
共有 \(M\) 张选票 \(1,2,\dots,M\),第 \(j\) 张选票上写有一个由小写英文字母组成的字符串 \(T_j\)。
若存在名字与 \(T_j\) 完全相同的候选人,则该候选人获得 \(1\) 票。否则,若存在恰好一名候选人的名字可以通过改动 \(T_j\) 中的 \(1\) 个字符得到,则该候选人获得 \(1\) 票。否则,这张选票作废。
对于每个 \(i=1,2,\dots,N\),求候选人 \(i\) 获得的票数。
约束
- \(1 \leq N \leq 100\)
- \(1 \leq M \leq 100\)
- \(1 \leq |S_i| \leq 10\)
- 若 \(i \neq j\),则 \(S_i \neq S_j\)。
- \(1 \leq |T_j| \leq 10\)
- \(S_i\) 与 \(T_j\) 均为由小写英文字母组成的字符串。
- \(N\) 与 \(M\) 为整数。
输入
输入从标准输入按以下格式给出:
\(N\) \(M\)
\(S_1\)
\(S_2\)
\(\vdots\)
\(S_N\)
\(T_1\)
\(T_2\)
\(\vdots\)
\(T_M\)
输出
共输出 \(N\) 行。
第 \(i\) 行应包含候选人 \(i\) 获得的票数。
思路
数据范围很小,因此直接按题意暴力模拟即可:对每张选票,把所有候选人的名字都扫一遍,判断它和选票之间的距离。
定义函数 check(s, name) 返回把选票字符串 \(s\) 改成候选人名字所需修改的字符数,即两串的逐位不同字符个数。若两者长度不同,则无论改几个字符都不可能变成对方,直接返回一个无效值(如 \(-1\))排除掉。
对一张选票,遍历所有候选人:
- 若
check返回 \(0\),说明名字与选票完全相同,按题意这位候选人直接得票,可以立刻结束对这张选票的处理; - 若返回 \(1\),说明恰好只差一个字符,先用
id记下这位候选人的下标,同时cnt累加这样的候选人数; - 其余情况(长度不同或差异超过 \(1\) 个字符)均不考虑。
遍历结束后,只有当 cnt == 1 时才给 id 对应的候选人加上一票;cnt == 0 表示没有任何候选人能被改成选票,cnt >= 2 表示有多个候选人满足条件,这两种情况都按题意作废。
时间复杂度 \(O(NML)\),其中 \(L\) 表示字符串长度。
代码
int n, m;
string name[105];
int score[105]; // score[i] -> name[i] 获得的选票数
// 求将 a 变成 b 需要修改的字符数量
int check(string a, string b)
{
if(a == b)
return 0;
if(a.size() != b.size())
return -1;
int cnt = 0;
for(int i = 0; i < a.size(); i++)
cnt += (a[i] != b[i]);
return cnt;
}
// 处理名字为 s 的选票
void vote(string s)
{
int id, cnt = 0;
// 如果存在仅修改一个字符就能得到的名字
// 用 id 记录该名字对应的下标,cnt 记录有多少个这样的名字
for(int i = 1; i <= n; i++)
{
int r = check(s, name[i]);
if(r == 0) // 就是对应名字
{
score[i]++;
return;
}
if(r == 1) // 仅需修改一次
{
id = i;
cnt++;
}
}
if(cnt == 1) // 只有一个名字能通过修改一个字符得到
score[id]++;
}
void solve()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
cin >> name[i];
for(int i = 1; i <= m; i++)
{
string s;
cin >> s;
vote(s);
}
for(int i = 1; i <= n; i++)
cout << score[i] << "\n";
}
C - Self-Stopping Shower
- 预估难度:
普及- - 标签:排序、数学、贪心
题面
问题陈述
高桥正在公共浴场使用淋浴。这个淋浴有一个按钮。若在时刻 \(t\) 按下按钮,则从时刻 \(t\) 到时刻 \(t+K\) 的 \(K\) 秒内会持续出热水。即使在出热水期间再次按下按钮,也会从最后一次按下按钮的时刻起再持续出 \(K\) 秒热水。
有 \(N\) 个青木,每个青木会各按一次按钮。第 \(i\) 个青木在时刻 \(A_i\) 按下按钮。
求为了使热水从时刻 \(0\) 到时刻 \(T\) 始终处于流出状态,高桥至少需要按下按钮多少次。这里,按下按钮的瞬间以及最后一次按下按钮后恰好 \(K\) 秒的瞬间,均视为正在出热水。
约束
- \(1 \leq N \leq 2 \times 10^5\)
- \(1 \leq K \leq 10^9\)
- \(1 \leq T \leq 10^9\)
- \(0 \leq A_i \leq 10^9\)
- 输入的所有值均为整数。
输入
输入从标准输入按以下格式给出:
\(N\) \(K\) \(T\)
\(A_1\) \(A_2\) \(\ldots\) \(A_N\)
输出
在一行中输出答案。
思路
每个青木按按钮都可以看成一段长度为 \(K\) 的热水区间 \([A_i, A_i+K]\)(两端都算正在出热水)。我们要做的就是让这些区间并起来能够完整覆盖 \([0,T]\),而高桥自己按按钮,则是用来填补区间之间出现的"断水空隙"。
先把所有 \(A_i\) 排序。排序后相邻区间一旦有重叠部分(\(A_i + K \ge A_{i+1}\)),中间就不会断水,可以直接跳过;而若 \(A_i + K \lt A_{i+1}\),说明中间有一段长度为 \(A_{i+1} - A_i - K\) 的空隙会停水,这就是需要额外按按钮的部分。
对于一段长度为 delta 的没有水的空隙而言,贪心可得,我们应当从空隙的起始时间开始,每隔恰好 \(K\) 秒按一次按钮,才能够使得额外按按钮的次数尽可能少。因此需要额外按按钮的次数可以通过 \(\lceil\dfrac{\text{delta}}{K}\rceil\) 计算获得。
最后单独考虑两段:
- 开头 \(0 \sim A_1\):第一个青木按下之前没有任何热水,因此会产生 \(A_1\) 秒的间隙。
- 结尾:最后一次有效按键之后到 \(T\) 的这部分间隙。
对于结尾部分需要注意,有可能某些按钮按下的时刻大于等于 \(T\),明显这部分按钮对答案是没有帮助的,因此可以直接舍弃。然后为了计算方便,我们可以假设在时刻 \(T\) 有一个虚拟的青木按了一次按钮(实际有没有均不影响答案),这样最后这一段也就可以当成"两个相邻按键之间的空隙",可以直接沿用前面的做法,不需要额外特判。
最后,把所有空隙的 \(\lceil\dfrac{\text{delta}}{K}\rceil\) 累加即为答案。时间复杂度 \(O(N\log N)\)。
代码
int n, k, t;
int a[200005];
// 如果有 delta 秒没有热水用,计算需要额外按多少次
// 即 delta / k 向上取整
int cal(int delta)
{
return (delta + k - 1) / k;
}
void solve()
{
cin >> n >> k >> t;
for(int i = 1; i <= n; i++)
cin >> a[i];
sort(a + 1, a + n + 1);
// 可能某几次按按钮的时间 > t,但无需考虑
while(n >= 1 && a[n] >= t)
n--;
a[n + 1] = t; // 假设第 t 秒也按了按钮,方便计算最后面的间隔
int cnt = 0; // 需要额外按按钮的次数
cnt += cal(a[1]); // 0 ~ a[1] 的间隙
for(int i = 1; i <= n; i++)
{
// 第 i 次按按钮后,出热水的时间能持续到下一次按按钮,则无需额外按
if(a[i] + k >= a[i + 1])
continue;
// 否则,两次按按钮的间隔时间为 a[i+1] - a[i]
// 除了持续出热水的 k 秒以外,剩余时间就是没有热水的间隙
cnt += cal(a[i + 1] - a[i] - k);
}
cout << cnt;
}
D - Concatenation of Prefixes
- 预估难度:
普及 - 标签:前缀和、二分、数学
题面
问题陈述
给定一个长度为 \(N\) 的整数序列 \((A_1,\dots,A_N)\)。
按以下步骤构造一个长度为 \(\frac{N(N+1)}{2}\) 的序列 \(B\)。
- 初始时,令 \(B\) 为空序列。
- 按 \(i=1,2,\dots,N\) 的顺序,依次将 \((A_1,\dots,A_i)\) 追加到 \(B\) 的末尾。
给定 \(Q\) 个以下形式的询问,请回答它们。
询问:给定 \(L\) 和 \(R\)。求 \(B_L+B_{L+1}+\dots+B_R\)。
约束
- \(1 \leq N \leq 10^5\)
- \(1 \leq Q \leq 10^5\)
- \(-10^8 \leq A_i \leq 10^8\)
- 对于每个询问,\(1\leq L\leq R \leq \frac{N(N+1)}{2}\)。
- 输入的所有值均为整数。
输入
输入从标准输入按以下格式给出:
\(N\) \(Q\)
\(A_1\) \(A_2\) \(\dots\) \(A_N\)
\(\mathrm{query}_1\)
\(\mathrm{query}_2\)
\(\vdots\)
\(\mathrm{query}_Q\)
这里,\(\mathrm{query}_i\) 表示第 \(i\) 个询问,按以下格式给出:
\(L\) \(R\)
输出
输出 \(Q\) 行。
第 \(i\) 行应包含对第 \(i\) 个询问的答案。
思路
我们记一个过程序列 \(C = (C_1, C_2, \ldots, C_N)\),其中 \(C_i = (A_1, A_2, \ldots, A_i)\)。那么题目中 \(B\) 序列的构造过程就可以描述成:把 \(C_1, C_2, \ldots, C_N\) 按顺序进行前后拼接。
由于 \(C_i\) 的长度为 \(i\),因此前 \(t\) 个 \(C_i\) 的总长度也就是 \(1+2+\ldots + t = \dfrac{(1+t)\cdot t}{2}\)。
对于每个给定的问题 \(\sum\limits_{i=L}^{R} B_i\),借助前缀和思想将其拆成 \(\sum\limits_{i=1}^R B_i - \sum\limits_{i=1}^{L-1} B_i\),那么只要能快速求出 \(B\) 序列的前缀和,就能直接得出问题的答案。
若想求出 \(B\) 序列的前 \(m\) 项和,我们可以先求出对应的最大项 \(B_m\) 来源于 \(C\) 中的哪个过程序列,也就是先找到一个整数 \(t\),满足:
那么 \(B\) 序列的前 \(m\) 项和便可以看作是由以下两部分相加组成:
- \(C_1, C_2, \ldots, C_t\) 这些过程序列的总和;
- \(C_{t+1}\) 这个序列的前 \(m - \dfrac{(1+t)\cdot t}{2}\) 项和,也就是原本 \(A\) 序列的前 \(m - \dfrac{(1+t)\cdot t}{2}\) 项和。
对于以上第二部分,只需要开一个前缀和数组 s[] 记录 \(A\) 序列的前缀和,即可快速求出。
而对于以上第一部分,由于 \(C_i\) 的总和也就是 \(A\) 序列的前 \(i\) 项和,即 s[i],那么只需要再对 s[] 做二维前缀和记作 p[],即可借助 p[t] 快速求出。
时间复杂度 \(O(N+Q \log N)\)。
代码
typedef long long ll;
int n, q, a[100005];
ll s[100005]; // s[i] 记 a[] 的前缀和
ll p[100005]; // p[i] 记 s[] 的前缀和
// 求前 m 项的总和
ll query(ll m)
{
// 二分求最大的 t 满足 1+2+...+t <= m
ll l = 1, r = n, t = 0;
while(l <= r)
{
ll mid = (l + r) / 2;
if((1 + mid) * mid / 2 <= m)
{
t = mid;
l = mid + 1;
}
else
r = mid - 1;
}
return p[t] + s[m - (1 + t) * t / 2];
}
void solve()
{
cin >> n >> q;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
s[i] = s[i - 1] + a[i];
p[i] = p[i - 1] + s[i];
}
while(q--)
{
ll l, r;
cin >> l >> r;
cout << query(r) - query(l - 1) << "\n";
}
}
E - Product-Bounded Arithmetic Sequences
- 预估难度:
普及+/提高- - 标签:枚举、数学、二分
题面
问题陈述
若一个正整数序列 \(A=(A_1,\dots,A_{|A|})\) 满足以下全部条件,则称其为好序列。
- \(|A|\geq 3\)
- \(A\) 是等差数列;即对所有 \(1 \leq i \leq |A|-1\) 都有 \(A_{i+1}-A_{i}=A_2-A_1\)。
- \(A_2-A_1 > 0\)
求所有元素之积不超过 \(N\) 的好序列的个数。
可以证明,在给定约束下答案不超过 \(10^{18}\)。
约束
- \(1 \leq N \leq 10^{18}\)
- \(N\) 为整数。
输入
输入从标准输入按以下格式给出:
\(N\)
输出
输出答案。
思路
可以发现合法序列仅由 首项 \(a\)、公差 \(d\)、项数 \(k\) 决定,且根据题意可得 \(a \ge 1, d \ge 1, k \ge 3\)。那么我们只需要统计有多少三元组 \((a, d, k)\) 满足下式即可:
考虑先确定 \(k\) 的范围。可以发现当 \(a=d=1\) 时 \(k\) 能取得最大值,但此时等差序列的乘积就是 \(k!\),因此 \(k\) 的最大值满足 \(k! \le N \le 10^{18}\),即 \(k \le 19\),范围很小,可以枚举。
然后考虑剩下的两项 \(a\) 和 \(d\) 要再枚举谁:
- 对于 \(a\),由于 \(a^k \le \prod\limits_{i=0}^{k-1} (a + i\cdot d) \le N\),根据 \(a^k \le N\) 可得 \(a \le \sqrt[k]{N}\),发现在 \(k=3\) 时取到最大值 \(N^{\frac 1 3} \le 10^6\),且随着 \(k\) 变大而迅速降低。如果枚举 \(a\),枚举次数将会是 \(\sum\limits_k \sqrt[k]{N}\)。
- 对于 \(d\),在 \(a=1, k=3\) 时,根据 \((1+d)(1+d^2) \le N\) 可得 \(d \approx 7 \times 10^8\)。
因此接下来再考虑枚举 \(a\),然后在 \(k, a\) 确定后,二分求出最大的项数 \(d_{\text{max}}\),即可得出目前对应的三元组数量。
至于枚举和二分过程中的合法性检查,可以将当前要判断的首项 \(a\)、公差 \(d\)、项数 \(k\) 传递给一个函数,然后模拟等差数列顺序相乘,过程中判断是否超过 \(N\) 的范围即可,时间复杂度 \(O(k)\),注意乘法可能超出 long long 范围,推荐使用 __int128,且在乘法过程中马上对范围进行判断。
时间复杂度 \(O(\sum\limits_{k} \sqrt[k]{N} \log N)\),由 \(k = 3\) 主导,即 \(O(\sqrt[3]{N}\log N)\)。
代码
typedef long long ll;
ll N;
// 检查首项为 a,公差为 d,项数为 k 的等差数列 乘积是否 <= N
bool check(ll a, ll d, int k)
{
__int128 m = 1;
for(int i = 0; i < k; i++)
{
m *= a + i * d;
if(m > N)
return false;
}
return true;
}
void solve()
{
cin >> N;
ll ans = 0;
for(int k = 3; check(1, 1, k); k++) // 枚举项数
for(int a = 1; check(a, 1, k); a++) // 枚举首项
{
// 二分找最大公差 d
ll l = 1, r = 1e9, d = 0;
while(l <= r)
{
ll mid = (l + r) / 2;
if(check(a, mid, k))
{
d = mid;
l = mid + 1;
}
else
r = mid - 1;
}
// 1 ~ d 均可作为公差使用
ans += d;
}
cout << ans;
}

浙公网安备 33010602011771号