2024-09-11-ABC370
A - Raise Both Hands
主要是读题。
输入两个数,10输出yes,01输出no,11或者00输出invalid
#include <bits/stdc++.h>
using namespace std;
int main() {
int L, R;
cin >> L >> R;
if (L == 1 && R == 0) {
cout << "Yes\n";
} else if (L == 0 && R == 1) {
cout << "No\n";
} else {
cout << "Invalid\n";
}
return 0;
}
B - Binary Alchemy
给定物品合成成分表aij表示物品i和物品j合成物品 aij。
问物品1,依次与 1,2,3,..N物品合成,问最后的物品。
4
3
2 4
3 1 2
2 1 2 4
2
按照题意模拟即可
#include <bits/stdc++.h>
using namespace std;
int main() {
int N;
cin >> N;
vector A(N, vector<int>(N));
for (int i = 0; i < N; i++) {
for (int j = 0; j <= i; j++) {
cin >> A[i][j];
A[i][j]--;
A[j][i] = A[i][j];
}
}
int x = 0;
for (int i = 0; i < N; i++) {
x = A[x][i];
}
cout << x + 1 << "\n";
return 0;
}
C - Word Ladder
有两个长度相等的字符串S和T,每次操作可以更改S中的一个字母,并将当前这次操作后的字符串S加入到一个新的字符串数组X的末尾。问最小的操作次数,并在最小的操作次数下,找出字典序最小的X。
adbe
bcbc
3
acbe
acbc
bcbc
首先最小的操作次数是一定的,就是两个字符串的不同的字母个数。
然后考虑修改字母的顺序来使X的字典序尽可能的小,一些字母是Si>Ti,一些字母是Si<Ti,即一个改了之后字典序变小了,一个改了之后字典序变大了,那么要使序列X的字典序尽可能小,那肯定是要先让S的字典序降下来,也就是改尽可能靠前的,能使字典序变小的字母,然后再改尽可能靠后的,会使字典序变大的字母,这个序列才是最佳的序列。
#include <bits/stdc++.h>
using namespace std;
int main() {
string S, T;
cin >> S >> T;
vector<string> ans;
const int N = S.size();
for (int i = 0; i < N; i++) {
if (S[i] > T[i]) {
S[i] = T[i];
ans.push_back(S);
}
}
for (int i = N - 1; i >= 0; i--) {
if (S[i] < T[i]) {
S[i] = T[i];
ans.push_back(S);
}
}
cout << ans.size() << "\n";
for (const auto &s : ans) {
cout << s << "\n";
}
return 0;
}
D - Cross Explosion
二维网格,初始每个格子有墙。
依次进行q次放炸弹的操作,给定每次放炸弹的位置(i,j),如果该位置有墙,则该墙消失。
否则,炸弹会爆炸,会产生十字冲击波,该位置上下左右的各第一个墙都会消失。
问最后还存在的墙的数量。
题目已知H*W并不大,所以可以开二维数组记录每一个位置是否有炸弹(也可以不开)。
然后问题就是如何快速找到一个位置的上下、左右的第一个墙,不难想到对于每一行和每一列开一个set,利用set的upperbound找到离这个位置最近的墙,然后利用set的erase删除即可。
#include <bits/stdc++.h>
using namespace std;
int h, w, q;
set<int> row[400005], col[400005];
int main() {
cin >> h >> w >> q;
for (int i = 1; i <= h; i++) {
for (int j = 1; j <= w; j++) {
row[i].insert(j);
col[j].insert(i);
}
}
while(q--)
{
int x,y;
cin>>x>>y;
if(*row[x].lower_bound(y)==y) {
row[x].erase(y);
col[y].erase(x);
} else {
auto it=row[x].lower_bound(y);
if(it!=row[x].end()) {
col[*it].erase(x);
row[x].erase(it);
}
it=row[x].lower_bound(y);
if(it!=row[x].begin()) {
it--;
col[*it].erase(x);
row[x].erase(it);
}
it=col[y].lower_bound(x);
if(it!=col[y].end()) {
row[*it].erase(y);
col[y].erase(it);
}
it=col[y].lower_bound(x);
if(it!=col[y].begin()) {
it--;
row[*it].erase(y);
col[y].erase(it);
}
}
}
int ans=0;
for(int i=1;i<=h;i++) ans+=row[i].size();
cout<<ans;
return 0;
}
E - Avoid K Partition
给定一个数组a,划分成若干个子区间,使得没有子区间的和为k。
求划分方案数。
考虑朴素的dp,dpi表示前i个数字的划分方案数,那么转移肯定是枚举上一段的位置:
这个方法是n方的,难点在于最后一段的和不能等于k,不然的话dpi直接就等于dp的前缀和了。
如果是前缀和的话,则表示最后一段的和为任意值都可以的总方案数,我们要想办法减掉最后一段和为k的方案数,那么我们反过来思考,设a1~ai的总和为s,最后一段要是k,那么之前的和就是s-k,所以其实就是剪掉前缀的和为s-k的方案数,也就是维护一个cnt[i]表示前缀和为i的方案数,这个开个map记一下就好了。
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int mo = 998244353;
int main() {
int n;
LL k;
cin >> n >> k;
vector<int> a(n);
for(int i=0;i<n;i++)
cin>>a[i];
map<LL, int> cnt;
cnt[0] = 1;
LL presum = 0;
int precnt = 1;
int ans = 0;
for (auto& i : a) {
presum += i;
ans = (precnt - cnt[presum - k] + mo) % mo;
cnt[presum] = (cnt[presum] + ans) % mo;
precnt = (precnt + ans) % mo;
};
cout << ans << '\n';
return 0;
}
F - Cake Division
给定一个环形数组,划分为k段,使得每段和的最小值最大。
在该最大值的各种划分方案中,求有多少位置,在所有划分方案中都不被分开。
5 2
3 6 8 6 4
13 1
这题通过的人很少。
我们在之前的abc讲过,遇到这种环形的问题,我们可以把数组复制一遍接在后面。
首先考虑怎么求得最大的区间和最小值,设这个区间和为x,那么也就是至少有一种合法的划分方案(即划分段数为k),使其中的每一段和都大于等于x。那么我们不难发现x是具有单调性的,x小的时候这个条件很容易满足,而x超过一个值之后就不能达到这个目标了,我们要求的就是这个值,所以这显然是一个二分答案。
然后考虑二分出x后,怎么检查这个加粗的条件是否可以达到,一个简单的方法是贪心:
我们首先计算一个数组fi表示从第i个数出发,满足和刚好大于x的区间的右端点,这个数组可以使用滑动窗口来完成。
比如:2 9 8 1 7 9 1 3 5 8 , x=13
那么我们首先复制一遍数组2 9 8 1 7 9 1 3 5 8 2 9 8 1 7 9 1 3 5 8,然后使用滑动窗口计算出f数组2 3 4 5.......
有了这个数组之后,我们的主要思想是刚大于等于x时就分段(因为没必要再加多余的数进当前段,把数留给后面的段肯定更好),所以我们只要枚举一个起点,然后每次通过f数组向后跳转到每一个段的右端点,如果跳了k段后,我们当前的右端点到左端点的距离是大于等于n的(即跑了一圈),那么从当前起点开始划分的方案就是可行的,当前的x也是可行的。枚举起点是O(n),跳一圈是O(k),所以检查的时间复杂度为O(nk),还无法通过这道题。
那么遇到这种“跳”的问题,我们可以联想到倍增,因为一个一个跳右端点实在是太慢了。设数组jumpij表示从第i个点,向后的第2^j个段的右端点位置,jump数组可以这么得到:
jump[i][0]=f[i]//f就是往后一个区间,所以就是2的0次方
jump[i][j]=jump[jump[i][j-1]][j-1]//相当于想要得到2的j次方,其实就是向后先跳2的j-1次方,再从这个位置在往后跳2的j-1次方
有了jump数组之后,往后跳k个区间只要把k拆成二进制,然后一个一个跳就行了,这样时间复杂度就缩小到了O(n log k),可以通过了。
然后还问了有多少个点是不会被断开的,这个比较简单,当以当前点为起点时是不可行的,但是在同一个x下有可行的,当前点就是不能被断开的,统计一下有多少个不可行的起点即可。
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int a[400005], N, n, k;
int up[400005][30];
int check (int x) {
memset(up,0,sizeof(up));
up[N][0] = N + 1;
up[N + 1][0] = N + 1;
queue<int> windows;
int r = 0;
int sum = 0;
for (int i = 0; i < N; ++i) {
while (r < N && sum < x) {
windows.push(a[r]);
sum += a[r];
++r;
}
if (sum < x)
up[i][0] = N + 1;
else
up[i][0] = r;
sum -= windows.front();
windows.pop();
}
for (int i = 1; i < 20; ++i)
for (int j = 0; j < N + 2; ++j) {
up[j][i] = up[up[j][i - 1]][i - 1];
}
int cnt = 0;
for (int i = 0; i < n; ++i) {
int pos = i;
for (int j = 0; j < 20; ++j) {
if (k & (1 << j)) {
pos = up[pos][j];
}
}
if (pos <= i + n) {
++cnt;
}
}
return cnt;
}
int main() {
cin >> n >> k;
for (int i = 0; i < n; ++i) {
cin >> a[i];
a[i + n] = a[i];
}
N = n + n;
int l = 1, r = 2e9 + 8;
while (l + 1 < r) {
int mid = l + (r - l) / 2;
if (check(mid))
l = mid;
else
r = mid;
}
int cnt = check(l);
cout << l << ' ' << n - cnt << '\n';
return 0;
}