AtCoder Beginner Contest 417 ABCDEF 题目解析
A - A Substring
题意
给定一个长度为 \(N\) 的字符串 \(S\),去掉其中 \(A\) 个前面的字符和 \(B\) 个后面的字符,输出剩余的字符串。
代码
int n, a, b;
char s[105];
int main()
{
cin >> n >> a >> b;
cin >> s;
for(int i = a; i < n - b; i++)
cout << s[i];
return 0;
}
B - Search and Delete
题意
给定一个长度为 \(N\) 的非递减序列 \(A\)。
有 \(M\) 次操作,第 \(i\) 次操作请从序列 \(A\) 中删除一个 \(B_i\)。如果不存在,则跳过这一次操作。
问做完 \(M\) 次操作之后的序列 \(A\)。
思路一
因为序列 \(A\) 是非递减的,所以删除数字的顺序可以不用管,我们只需要知道每种数字分别被删除了多少次即可。
可以采用双指针的方法找出每种数字 \(X\) 原本出现了多少次,然后再去 \(B\) 数组里数出现了多少次,数量相减后,按照剩余数量输出该数字 \(X\) 即可。
代码一
int n, m;
int a[105], b[105];
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++)
cin >> a[i];
for(int i = 1; i <= m; i++)
cin >> b[i];
for(int i = 1; i <= n; i++)
{
int j = i; // j 表示与 a[i] 相同的最后一个数字所在位置
while(j + 1 <= n && a[j + 1] == a[i])
j++;
int cnt = j - i + 1; // a[i] 数字出现了 j-i+1 次
i = j; // 直接移动 i,表示跳过 a[i] 这一种数字
for(int k = 1; k <= m; k++)
if(b[k] == a[i]) // 需要删除一次 a[i]
cnt--;
for(int k = 1; k <= cnt; k++) // 根据剩余数量输出 a[i] 即可
cout << a[i] << " ";
}
return 0;
}
思路二
直接采用 STL 的 map 容器统计每种数字剩余出现次数即可。
代码二
int main()
{
int n, m;
cin >> n >> m;
map<int, int> mp;
for(int i = 1; i <= n; i++)
{
int x;
cin >> x;
mp[x]++;
}
for(int i = 1; i <= m; i++)
{
int x;
cin >> x;
mp[x]--;
}
for(auto it : mp)
{
for(int i = 1; i <= it.second; i++)
cout << it.first << " ";
}
return 0;
}
C - Distance Indicators
题意
给定一个长度为 \(N\) 的整数序列 \(A\)。
问有多少对 \((i, j)\) 满足 \(1 \le i \lt j \le N\) 且 \(j-i = A_i + A_j\)。
思路
即对于每个位置 \(j\),计算下标与值的差值 \(j - A_j\),然后找在此之前有多少个位置 \(i\) 满足 \(i + A_i\) 与该差值相等即可。
由于 \(A_i\) 较小,\(i+A_i \le 4\times 10^5\),可以采用计数数组统计。
时间复杂度 \(O(N)\)。
代码
int n, a[200005];
int cnt[400005]; // 统计 i+a[i] 出现的次数
long long ans = 0;
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> a[i];
if(i - a[i] > 0) // 以当前 i 当作式子内的 j
ans += cnt[i - a[i]];
cnt[i + a[i]]++;
}
cout << ans;
return 0;
}
D - Takahashi's Expectation
题意
高桥会按顺序收到 \(N\) 件礼物,每件礼物有三个属性 \(P_i, A_i, B_i\)。
当他收到第 \(i\) 件礼物时,如果他当前的心情 \(\ge P_i\),那么他的心情会增加 \(A_i\);如果他当前的心情 \(\lt P_i\),那么他的心情会减少 \(B_i\)(如果减少后心情 \(\lt 0\),则心情会变成 \(0\))。
有 \(Q\) 个问题,每个问题给定一个 \(X_i\),表示当高桥一开始的心情为 \(X_i\) 时,最终心情是多少?
思路
直接模拟的复杂度是 \(O(Q\cdot N)\),会超时。
但注意到 \(1 \le P_i, A_i, B_i \le 500\) 这一条件,也就是说如果心情 \(\gt 500\),那么心情一定是会一直减少的。
因此当初始心情 \(\gt 500\) 时,我们可以考虑借助对 \(B\) 数组进行前缀和+二分来快速找出从哪一轮开始会让心情变为 \(\le 500\),然后再从那一轮开始往后推即可。
但每次都往后推到第 \(N\) 项显然是不现实的,所以可以考虑预处理。
记 f[i][j] 表示在收到第 \(i\) 件礼物之前,且高桥的心情为 \(j\) 时,最终他的心情是多少。
考虑转移:
- 如果
p[i] >= j,此时收到礼物后高桥会高兴,心情变为j + a[i],最终答案等同于f[i + 1][j + a[i]]。 - 如果
p[i] < j,此时收到礼物后高桥会不高兴,心情变为max(0, j - b[i]),最终答案等同于f[i + 1][max(0, j - b[i])]。
所以我们只需要考虑倒推每一轮的结果即可。
考虑初始条件,即超出第 \(n\) 轮后不会再导致心情发生变动,因此 dp[n + 1][j] = j。
然后过程中 j + a[i] 这一步可能会超出 \(500\) 的范围,所以第二维推荐开 \(1000\) 进行处理。
总时间复杂度 \(O(N \cdot D + Q \log N)\),其中 \(D = 1000\)。
代码
int n;
int p[10005], a[10005], b[10005];
int s[10005]; // s[i] 表示如果前 i 件物品全导致心情下降,下降幅度是多少
int f[10005][1005];
// f[i][j] 表示从第 i 轮开始,且第 i 轮的心情为 j 时,最终心情是多少
int main()
{
cin >> n;
for(int i = 1; i <= n; i++)
{
cin >> p[i] >> a[i] >> b[i];
s[i] = s[i - 1] + b[i];
}
for(int j = 0; j <= 1000; j++)
f[n + 1][j] = j; // 超出 n 轮则心情固定
for(int i = n; i >= 1; i--)
for(int j = 0; j <= 1000; j++)
{
if(p[i] >= j)
f[i][j] = f[i + 1][j + a[i]];
else
f[i][j] = f[i + 1][max(0, j - b[i])];
}
int q;
cin >> q;
while(q--)
{
int x;
cin >> x;
if(x <= 1000)
cout << f[1][x] << "\n";
else
{
// 二分找从哪一轮开始可以让心情 <= 1000
int l = 1, r = n, pos = n+1;
while(l <= r)
{
int mid = (l + r) / 2;
if(x - s[mid - 1] <= 1000)
{
pos = mid;
r = mid - 1;
}
else
l = mid + 1;
}
if(pos > n) // 如果 n 轮结束后心情仍然超过 1000
cout << x - s[n] << "\n"; // 直接减去 n 轮的 b 数组总和
else // 否则,从第 pos 轮开始,初始心情减去前 pos-1 轮的 b 数组总和即可
cout << f[pos][x - s[pos - 1]] << "\n";
}
}
return 0;
}
E - A Path in A Dictionary
题意
给定一张连通的简单无向图 \(G\),包含 \(N\) 个点与 \(M\) 条边。
请找出图中字典序最小的一条从 \(X\) 到 \(Y\) 的简单路径。
思路
我们在使用深搜求解排列/组合问题时,可以根据每一步枚举的顺序来控制找出的答案的字典序,为了让最终答案字典序最小,只需要在过程中每一步都从小到大枚举即可。
本题可以借助深度优先搜索的性质,在存储完邻接表之后,为每个点的邻接表从小到大排个顺序,保证从某个点出发,一定是先搜索编号更小的相邻点,再去搜索编号较大的点,即可保证答案的字典序。
至于搜索过程,由于图是保证连通的,因此只要一个点此前已经被搜索过,但若没找到答案的话,那么这个点接下来便不会再被搜到,开个计数数组记录每个点的搜索情况即可。
单组数据时间复杂度 \(O(N)\)。
代码
int n, m, x, y;
vector<int> G[1005];
bool vis[1005];
int ans[1005];
bool dfs(int u, int step)
{
ans[step] = u;
vis[u] = true;
if(u == y)
{
for(int i = 1; i <= step; i++)
cout << ans[i] << " ";
cout << "\n";
return true;
}
for(int &v : G[u])
{
if(vis[v])
continue;
if(dfs(v, step + 1))
return true;
}
return false;
}
void solve()
{
cin >> n >> m >> x >> y;
for(int i = 1; i <= n; i++)
{
G[i].clear();
vis[i] = false;
}
for(int i = 1; i <= m; i++)
{
int u, v;
cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
for(int i = 1; i <= n; i++)
sort(G[i].begin(), G[i].end());
dfs(x, 1);
}
int main()
{
int T;
cin >> T;
while(T--)
solve();
return 0;
}
F - Random Gathering
题意
有 \(N\) 个盘子排成一排,从左到右分别编号为 \(1, 2, \dots, N\)。一开始,编号为 \(i\) 的盘子内有 \(A_i\) 块石头。
有 \(M\) 次操作,第 \(i\) 次操作给定两个正整数 \(L_i, R_i\):
- 移除编号在 \([L_i, R_i]\) 范围内的所有盘子中的石头。
- 随机选择一个在 \([L_i, R_i]\) 范围内的整数 \(x\)。
- 将所有第一步中移除的石头全部放到编号为 \(x\) 的盘子内。
对于 \(i = 1, 2, \dots, N\) 的每一个整数 \(i\),请求出最终第 \(i\) 个盘子内的石头数量期望值,对 \(998244353\) 取模。
思路
考虑操作,由于位置 \(x\) 是随机选择的,所以相当于每次选定一个区间 \([L, R]\),然后这个区间内每个位置的数字有 \(\dfrac{R-L}{R-L+1}\) 的概率分不到任何一个石头,有 \(\dfrac {1} {R-L+1}\) 的概率能分到区间内所有石头的总和。
记总和为 \(S\),则区间内每个位置在操作后的期望值均为 \(\dfrac{R-L}{R-L+1} \times 0 + \dfrac{1}{R-L+1} \times S = \dfrac{S}{R-L+1}\)。
所以需要有一个数据结构能够快速求出当前的区间总和,并快速将区间赋值为一个新值。考虑带懒惰标记的线段树即可。
时间复杂度 \(O(N\log N)\)。
代码
typedef long long ll;
const ll mod = 998244353;
ll qpow(ll a, ll n)
{
ll r = 1;
while(n)
{
if(n & 1)
r = r * a % mod;
a = a * a % mod;
n >>= 1;
}
return r;
}
#define ls (p << 1)
#define rs (p << 1 | 1)
struct node
{
int l, r;
ll sum;
ll lazyVal;
bool lazyTag;
};
node tr[200005 << 2];
int A[200005];
void push_up(int p)
{
tr[p].sum = (tr[ls].sum + tr[rs].sum) % mod;
}
void push_down(int p)
{
if(!tr[p].lazyTag)
return;
tr[ls].lazyTag = true;
tr[ls].lazyVal = tr[p].lazyVal;
tr[ls].sum = (tr[ls].r - tr[ls].l + 1) * tr[p].lazyVal % mod;
tr[rs].lazyTag = true;
tr[rs].lazyVal = tr[p].lazyVal;
tr[rs].sum = (tr[rs].r - tr[rs].l + 1) * tr[p].lazyVal % mod;
tr[p].lazyTag = false;
tr[p].lazyVal = 0;
}
void build(int l, int r, int p = 1)
{
tr[p].l = l;
tr[p].r = r;
tr[p].lazyVal = 0;
tr[p].lazyTag = false;
if(l == r)
{
tr[p].sum = A[l];
return;
}
int mid = (l + r) / 2;
build(l, mid, ls);
build(mid+1, r, rs);
push_up(p);
}
void update(int l, int r, ll val, int p = 1)
{
if(l <= tr[p].l && tr[p].r <= r)
{
tr[p].lazyTag = true;
tr[p].lazyVal = val;
tr[p].sum = (tr[p].r - tr[p].l + 1) * val % mod;
return;
}
push_down(p);
if(l <= tr[ls].r)
update(l, r, val, ls);
if(r >= tr[rs].l)
update(l, r, val, rs);
push_up(p);
}
ll query(int l, int r, int p = 1)
{
if(l <= tr[p].l && tr[p].r <= r)
return tr[p].sum;
push_down(p);
ll sum = 0;
if(l <= tr[ls].r)
sum += query(l, r, ls);
if(r >= tr[rs].l)
sum += query(l, r, rs);
return sum % mod;
}
int main()
{
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; i++)
cin >> A[i];
build(1, n);
while(m--)
{
int l, r;
cin >> l >> r;
ll v = query(l, r) * qpow(r - l + 1, mod - 2) % mod;
update(l, r, v);
}
for(int i = 1; i <= n; i++)
cout << query(i, i) << " ";
return 0;
}

浙公网安备 33010602011771号