A.Exchange(贪心)
题意:
日本有六种流通硬币:111日元、555日元、101010日元、505050日元、100100100日元和500500500日元。请回答下列有关这些硬币的问题。
AtCoder先生的钱包里有AAA个111日元硬币,BBB个555日元硬币,CCC个101010日元硬币,DDD个505050日元硬币,EEE个100100100日元硬币和FFF个500500500日元硬币。
他计划依次在NNN家商店购物。具体来说,他计划在第iii个商店(1≤i≤N)(1\leq i\leq N)(1≤i≤N)购买一件价格为XiX_iXi日元(含税)的商品。
给零钱和收零钱都需要时间,因此他想选择硬币,以便在每家商店都能正好支付所需要的金额。
请判断这是否可行。
分析:
本题考虑贪心,优先用价值较高的硬币去支付,同时将商品价格从大到小进行排序,遍历一遍即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL N = 25;
struct number {
int w, v;
} num[N];
int n, shop[N], tot, need;
bool cmp(int i, int j) {
return i > j;
}
void solve() {
for (int i = 1; i <= 6; i++) {
cin >> num[i].v;
tot += num[i].v * num[i].w;
}
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> shop[i];
need += shop[i];
}
if (tot < need) {
cout << "No" << endl;
return;
}
sort(shop + 1, shop + n + 1, cmp);
for (int i = 1; i <= n; i++)
for (int j = 6; j >= 1; j--) {
if (!shop[i])
break;
while (num[j].v > 0 && shop[i] - num[j].w >= 0) {
shop[i] -= num[j].w;
num[j].v--;
}
}
bool flag = false;
for (int i = 1; i <= n; i++)
if (shop[i]) {
flag = true;
break;
}
if (flag)
cout << "No" << endl;
else
cout << "Yes" << endl;
}
int main() {
num[1].w = 1, num[2].w = 5, num[3].w = 10, num[4].w = 50, num[5].w = 100, num[6].w = 500;
solve();
return 0;
}
B.Puzzle of Lamps(规律)
题意:
AtCoder先生创建了一个由NNN个小灯泡(从左到右排列成一排)和两个开关AAA和BBB组成的装置:“0”(关)和"1"(开)。按下每个开关会产生以下结果:
- 按下开关AAA会将最左边处于"0"状态的灯泡变成"1"。
- 按下开关BBB会将处于"1"状态的最左边灯泡变为"0"。
如果没有适用的灯泡,则无法按下开关。
最初,所有灯泡都处于"0"状态。他希望灯泡的状态从左到右为S1,S2,…,SNS_1,S_2,\dots,S_NS1,S2,…,SN。请确定按下开关的顺序和次数。按下的次数不一定要最少,但最多应为10610^6106,以便在实际时间内完成操作。可以证明,在该问题的约束条件下存在一个解。
分析:
注意到开关是规定从左至右依次控制灯。
意思就是,若单独讨论iii位置上的灯为状态111,它必然需要进行iii次操作AAA得到。显然,在nnn位置的灯满足这个条件。
而在之前的灯的状态可能会被后边的灯的状态所影响,分类讨论即可。
设当前iii位置状态为ppp,先前灯的状态为qqq
若p=0p=0p=0,
- 若q=0q=0q=0,直接继承,无需操作;
- 若q=1q=1q=1,需要进行iii次操作BBB使iii灯泡由"1"变为"0";
若p=1p=1p=1,
- 若q=1q=1q=1, 直接继承,无需操作;
- 若q=0q=0q=0,需要进行iii次操作AAA使iii号灯泡由"0"变为"1";
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL N = 1e6 + 10;
int n, cnt;
string s;
char ans[N];
void solve() {
cin >> n >> s;
int last = -1;
for (int i = n - 1; i >= 0; i--) {
if (i == n - 1) {
if (s[i] == '0')
last = 0;
if (s[i] == '1') {
last = 1;
for (int k = 1; k <= n; k++)
ans[++cnt] = 'A';
}
} else {
if (s[i] == '0') {
if (last == 0)
continue;
else {
last = 0;
for (int k = 1; k <= i + 1; k++)
ans[++cnt] = 'B';
}
} else {
if (last == 1)
continue;
else {
last = 1;
for (int k = 1; k <= i + 1; k++)
ans[++cnt] = 'A';
}
}
}
}
cout << cnt << endl;
for (int i = 1; i <= cnt; i++)
cout << ans[i];
cout << endl;
}
int main() {
solve();
return 0;
}
C.Routing(BFS、最短路)
题意:
有一个NNN行和NNN列的网格。设(i,j)(i,j)(i,j)(1≤i≤N,1≤j≤N)(1\leq i\leq N,1\leq j\leq N)(1≤i≤N,1≤j≤N)表示位于从上往下第iii行和从左往上第jjj列的单元格。每个单元格最初都被涂成红色或蓝色,如果ci,j=c_{i,j}=ci,j=R,表示单元格(i,j)(i,j)(i,j)是红色的,如果ci,j=c_{i,j}=ci,j=B,表示这个单元格是蓝色的。现在想将某些单元格的颜色改为紫色,以便同时满足以下两个条件:
条件1:从单元格(1,1)(1,1)(1,1)移动到单元格(N,N)(N,N)(N,N)时,只能经过红色或紫色的单元格。
条件2:只需经过蓝色或紫色单元格,即可从单元格(1,N)(1,N)(1,N)移动到单元格(N,1)(N,1)(N,1)。
这里的可以移动是指可以通过重复移动到水平或垂直相邻的相关颜色的单元格,从起点到达终点。
要满足这些条件,最少有多少个单元格必须变为紫色?
分析:
使用两次BFS或者最短路算法,分别从两个方向开始搜索,把网格看成图,每个点都和四个方向联通,联通时通过所需的距离取决于颜色是否符合要求,如果需要改变颜色,就是距离等于一;反之,等于零。把这个图搜一遍或跑一遍,求出(1,1)(1,1)(1,1)到(N,N)(N,N)(N,N)最少经过多少蓝色,以及(1,N)(1,N)(1,N)到(N,1)(N,1)(N,1)最少经过多少红色,相加即答案。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL N = 505;
char s[N][N];
int n, b[N][N], r[N][N];
bool vis[N][N];
int xz[] = {1, 0, -1, 0};
int yz[] = {0, 1, 0, -1};
struct node {
int x, y, d;
};
bool operator<(node a, node b) {
return a.d > b.d;
}
priority_queue<node> q;
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%s", s[i] + 1);
memset(b, 0x3f, sizeof(b));
memset(r, 0x3f, sizeof(r));
b[1][1] = (s[1][1] == 'B');
q.push({1, 1, b[1][1]});
while (!q.empty()) {
node now = q.top();
q.pop();
if (vis[now.x][now.y])
continue;
vis[now.x][now.y] = 1;
if (now.x == n && now.y == n)
continue;
for (int i = 0; i < 4; i++) {
int xx = now.x + xz[i];
int yy = now.y + yz[i];
if (xx < 1 || xx > n || yy < 1 || yy > n)
continue;
if (b[xx][yy] > now.d + (s[xx][yy] == 'B')) {
b[xx][yy] = now.d + (s[xx][yy] == 'B');
q.push({xx, yy, b[xx][yy]});
}
}
}
memset(vis, 0, sizeof(vis));
r[n][1] = (s[n][1] == 'R');
q.push({n, 1, r[n][1]});
while (!q.empty()) {
node now = q.top();
q.pop();
if (vis[now.x][now.y])
continue;
vis[now.x][now.y] = 1;
if (now.x == 1 && now.y == n)
continue;
for (int i = 0; i < 4; i++) {
int xx = now.x + xz[i];
int yy = now.y + yz[i];
if (xx < 1 || xx > n || yy < 1 || yy > n)
continue;
if (r[xx][yy] > now.d + (s[xx][yy] == 'R')) {
r[xx][yy] = now.d + (s[xx][yy] == 'R');
q.push({xx, yy, r[xx][yy]});
}
}
}
printf("%d\n", b[n][n] + r[1][n]);
return 0;
}
D.Earthquakes(单调栈、线段树)
题意:
AtCoder街是一条在平地上用直线表示的道路。路上竖立着NNN根电线杆,高度为HHH。电线杆按时间顺序编号为1,2,…,N1,2,\dots,N1,2,…,N。电线杆iii(1≤i≤N1\leq i\leq N1≤i≤N)垂直于坐标XiX_iXi。每根电线杆的底座都固定在地面上。
街道将经历NNN次地震。在第iii次地震(1≤i≤N)(1\leq i\leq N)(1≤i≤N)中,会发生以下事件:
- 如果电线杆iii尚未倒下,它将倒向左边或右边,每个概率为12\frac{1}{2}21。
- 如果一根倒下的电线杆与另一根尚未倒下的电线杆相撞(包括在电线杆底部相撞),后一根电线杆也会朝同一方向倒下。这可能会引发连锁反应。
在步骤1中,一根电线杆倒下的方向与其他电线杆倒下的方向无关。
下图是在一次地震中电线杆可能倒下的示例:

为了防备地震,对于每个t=1,2,…,Nt=1,2,\dots,Nt=1,2,…,N,求出在第ttt次地震中所有极点都倒下的概率。将其乘以2N2^N2N,结果对998244353998244353998244353取模。可以证明要输出的值是整数。
分析:
我们发现,所有的电线杆可以被划分为若干段。
定义一段为左端点的电线杆向右倒能让整段电线杆全部倒完的极长子区间。
不同段之间不会有任何影响,所以对于不存在连锁反应的区间,每个区间可以独立处理。因此,我们可以将原问题拆分为子问题:
有一段长度为lll位置升序的电线杆。
从左往右第iii个电线杆在第pip_ipi次地震中倒塌,求最后倒塌的电线杆是第iii个电线杆的概率。
我们发现任何时刻,一段区间均可被分成三部分:
- 向左倒塌的一部分
- 站立的一部分
- 向右倒塌的一部分
第iii个电线杆未倒塌,当且仅当:所有ppp中111到iii的前缀最小值都向左倒塌;所有ppp中lll到的iii后缀最小值都向右倒塌。
这些代表了在iii之前主动倒塌的电线杆(不是被其他推倒的)。
当且仅当iii未倒塌且iii是站立的一段的起点或终点,第iii个电线杆最后倒塌。
我们发现,第iii个电线杆是最后倒塌的概率为12a×b2\frac{1}{2^a}\times \frac{b}{2}2a1×2b。
其中aaa为ppp到iii的前缀最小值和后缀最小值的个数之和。
因为必须保持iii站立,所以左边的必须往左倒,右边的必须往右倒,概率为12a\frac{1}{2^a}2a1。
若iii为站立区间的左端点或右端点,b=1b=1b=1。
若iii是单独的一个(即既是左端点又是右端点),b=2b=2b=2。
若iii是左右端点中的一个,则iii倒下的方向有要求,概率为12\frac{1}{2}21。
若iii同时是左右端点(单独),则iii倒下的方向没有要求,概率为111。
不难发现,概率中aaa的求解过程可以使用单调栈解决。
由此,子问题得到解决。
合并子问题:设一共有ccc段,电线杆iii所在的段的编号为gig_igi。
时间ttt的答案为s1×s2×⋯×sgt−1×Z×sgt+1×⋯×scs_1\times s_2 \times \dots \times s_{g_{t}-1}\times Z \times s_{g_{t}+1}\times\dots\times s_cs1×s2×⋯×sgt−1×Z×sgt+1×⋯×sc。
其中ZZZ为子问题gtg_tgt中最后倒下的电线杆是ttt的概率,sis_isi为子问题iii中最后倒下的电线杆编号小于ttt的概率之和。
可以发现这是一个单点修改,区间查询问题,考虑使用线段树优化。
线段树中维护sss,每次将答案算出后,将ZZZ加到sgts_{g_t}sgt中。
注意:题目要求要将答案乘上2N2^N2N,但解决子问题时不能乘2N2^N2N,而要乘2l2^l2l,这样所有子问题乘起来才是2N2^N2N。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL N = 2e5 + 5;
const LL mod = 998244353;
struct segt {
struct node {
LL l, r, v;
} t[N << 2];
#define ls (p << 1)
#define rs (p << 1 | 1)
void build(LL p, LL l, LL r) {
t[p].l = l;
t[p].r = r;
t[p].v = 0;
if (l == r)
return;
LL mid = (l + r) >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
}
void add(LL p, LL id, LL v) {
if (t[p].l == t[p].r) {
t[p].v += v;
t[p].v %= mod;
return;
}
if (id <= t[ls].r)
add(ls, id, v);
else
add(rs, id, v);
t[p].v = t[ls].v * t[rs].v, t[p].v %= mod;
}
LL query(LL p, LL l, LL r) {
if (l <= t[p].l && t[p].r <= r)
return t[p].v;
LL res = 1;
if (t[ls].r >= l) {
res *= query(ls, l, r);
res %= mod;
}
if (t[rs].l <= r) {
res *= query(rs, l, r);
res %= mod;
}
return res;
}
} T;
struct Point {
LL x, y;
};
bool cmp(Point a, Point b) { return a.x < b.x; }
LL n, h, c, x[N], g[N], t[N], k[N], pow2[N];
Point a[N];
vector<LL> p[N];
vector<LL> res[N];
void solve(LL id) {
LL m = p[id].size() - 1;
stack<LL> stk;
for (LL i = 1; i <= m; i++) {
while (!stk.empty() && p[id][i] < stk.top())
stk.pop();
stk.push(p[id][i]);
k[i] = stk.size() - 1;
}
stack<LL> sstk;
for (LL i = m; i >= 1; i--) {
while (!sstk.empty() && p[id][i] < sstk.top())
sstk.pop();
sstk.push(p[id][i]);
k[i] += sstk.size() - 1;
}
res[id].emplace_back(0);
for (LL i = 1; i <= m; i++) {
LL b = (i == 1 || p[id][i - 1] < p[id][i]) + (i == m || p[id][i] > p[id][i + 1]);
res[id].emplace_back(b * pow2[m - k[i] - 1] % mod);
}
}
int main() {
cin >> n >> h;
pow2[0] = 1;
for (LL i = 1; i <= n; i++) {
cin >> x[i];
a[i].x = x[i];
a[i].y = i;
pow2[i] = (pow2[i - 1] << 1) % mod;
}
for (LL i = 1; i <= n; i++)
p[i].emplace_back(0);
sort(a + 1, a + n + 1, cmp);
g[a[1].y] = ++c,
p[c].emplace_back(a[1].y),
t[a[1].y] = p[c].size() - 1;
for (LL i = 2; i <= n; i++) {
if (a[i].x - a[i - 1].x <= h) {
g[a[i].y] = c;
p[c].emplace_back(a[i].y);
t[a[i].y] = p[c].size() - 1;
} else {
g[a[i].y] = ++c;
p[c].emplace_back(a[i].y);
t[a[i].y] = p[c].size() - 1;
}
}
for (LL i = 1; i <= c; i++)
solve(i);
T.build(1, 1, c);
for (LL i = 1; i <= n; i++) {
LL x = res[g[i]][t[i]];
LL ans = 1;
if (g[i] - 1)
ans *= T.query(1, 1, g[i] - 1);
if (g[i] + 1 <= c) {
ans *= T.query(1, g[i] + 1, c);
ans %= mod;
}
ans *= x;
ans %= mod;
T.add(1, g[i], x);
cout << ans << ' ';
}
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

805

被折叠的 条评论
为什么被折叠?



