A.Question Marks(思维)
题意:
蒂姆正在做一个由4n4n4n个问题组成的测试;每个问题有444个选项:“A”、“B”、“C"和"D”。每个选项都有nnn个正确答案,也就是说有nnn道题的答案是"A",nnn道题的答案是"B",nnn道题的答案是"C",nnn道题的答案是"D"。
对于每道题,蒂姆都会把答案写在答题纸上。如果想不出答案,他会在该题上留下问号?
他的答题纸有4n4n4n个字符。蒂姆最多能答对多少题?
分析:
本题我们使用map分别记录ABCD四个字符出现的次数,和nnn比较取较小值累加一下即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
void solve() {
LL n;
cin >> n;
string s;
cin >> s;
map<char, LL> m;
for (LL i = 0; i < s.size(); i++) {
if (m[s[i]])
m[s[i]]++;
else
m[s[i]] = 1;
}
LL ans = 0;
ans += min(m['A'], n);
ans += min(m['B'], n);
ans += min(m['C'], n);
ans += min(m['D'], n);
cout << ans << endl;
}
int main() {
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
B.Parity and Sum(贪心)
题意:
给定一个由nnn个正整数组成的数组aaa。
在一次操作中,你可以选取任意一对索引(i,j)(i,j)(i,j),使得aia_iai和aja_jaj具有不同的奇偶性,然后用它们的和替换较小的索引。更正式的说法是
- 如果是ai<aja_i\lt a_jai<aj,则用ai+aja_i+a_jai+aj替换aia_iai;
- 否则,用ai+aja_i+a_jai+aj替换aja_jaj。
求使数组中所有元素具有相同奇偶性所需的最少操作数。
分析:
由于奇数加偶数等于奇数,所以除了一开始全为偶数的情况,最后的结果必定是全为奇数。贪心的想,设xxx为当前最大的奇数,则对于一个偶数aia_iai必定与xxx进行操作。若ai<xa_i\lt xai<x,则进行一次操作。由于每个偶数至少操作一次,这样肯定不劣。我们想让这样的操作尽可能多,对aaa从小到大排序,依次处理。这样xxx会随着操作越来越大。
对于剩余的ai>xa_i>xai>x,发现只要进行两次操作就可以把其变为奇数。可以想到拉出剩余的最大的aia_iai进行操作,这样就可以尽可能增大xxx,使得其他小于aia_iai的数只需一次操作。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 200005;
int n, a[N];
void solve() {
cin >> n;
int mx = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
if (a[i] % 2 == 1)mx = max(mx, a[i]);
}
int tmp = a[1] % 2;
bool check = 1;
for (int i = 2; i <= n; i++) {
if (a[i] % 2 != tmp) {
check = 0;
break;
}
}
if (check) {
cout << "0" << endl;
return;
}
sort(a + 1, a + n + 1);
int ans = 0;
for (int i = 1; i <= n; i++) {
if (a[i] % 2 == 0) {
if (a[i] < mx) {
ans++;
a[i] += mx;
mx = max(mx, a[i]);
a[i] = -1;
}
}
}
for (int i = n; i >= 1; i--) {
if (a[i] % 2 == 0 && a[i] != -1) {
if (a[i] < mx) {
ans++;
a[i] += mx;
mx = max(mx, a[i]);
} else {
ans += 2;
mx += a[i];
a[i] += mx;
mx = max(mx, a[i]);
}
}
}
cout << ans << endl;
}
int main() {
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
C.Light Switches(模拟)
题意:
有一间公寓由nnn个房间组成,每个房间的灯最初都是关着的。
为了控制这些房间的灯光,公寓的主人决定在房间里安装芯片,这样每个房间正好有一个芯片,芯片安装的时间各不相同。具体来说,这些时间由数组a1,a2,…,ana_1,a_2,\ldots,a_na1,a2,…,an表示,其中aia_iai是在第iii个房间安装芯片的时间(以分钟为单位)。
芯片一经安装,就会每隔kkk分钟改变房间的灯光状态–在kkk分钟内开灯,然后在接下来的kkk分钟内关灯,再在接下来的kkk分钟内开灯,以此类推。换句话说,芯片在aia_iai、ai+ka_i+kai+k、ai+2ka_i+2kai+2k、ai+3ka_i+3kai+3k、…\ldots…分钟时改变第iii个房间的灯光状态。
公寓里所有房间最早亮灯的时刻是什么时候?
分析:
读题发现,灯的开关存在循环,最终的答案一定会在最后一个亮起的灯亮起的时间里。
如果我们用ttt表示最后一个给房间安装芯片的时刻,那么答案一定在ttt至t+k−1t+k−1t+k−1之间。因此,我们定义两个指针lll和rrr并遍历所有的aia_iai,计算当前灯在ttt至t+k−1t+k−1t+k−1之间的亮起时间,同时更新lll和rrr。
最后,如果l≤rl≤rl≤r,则存在所有的灯都同时亮起的时刻,因此输出lll,否则不存在这样的时刻,输出−1−1−1。
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 200005;
int n, k, a[N];
void solve() {
int t = 0;
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
t = max(t, a[i]);
}
int l = t, r = t + k - 1;
for (int i = 1; i <= n; i++) {
if (((t - a[i]) / k) % 2 == 1)
l = max(l, a[i] + ((t - a[i]) / k + 1) * k);
else
r = min(r, a[i] + ((t - a[i]) / k + 1) * k - 1);
}
if (l <= r)
cout << l << endl;
else
cout << -1 << endl;
}
int main() {
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
D.Med-imize(动态规划)
题意:
给定两个正整数nnn和kkk以及另一个由nnn个整数组成的数组aaa。
在一次操作中,可以选择aaa中任意一个大小为kkk的子数组,然后将其从数组中删除,而不改变其他元素的顺序。更正式地说,假设(l,r)(l,r)(l,r)是对子数组al,al+1,…,ara_l,a_{l+1},\ldots,a_ral,al+1,…,ar的操作,使得r−l+1=kr-l+1=kr−l+1=k,那么执行此操作意味着将aaa替换为[a1,…,al−1,ar+1,…,an][a_1,\ldots,a_{l-1},a_{r+1},\ldots,a_n][a1,…,al−1,ar+1,…,an]。
例如,如果a=[1,2,3,4,5]a=[1,2,3,4,5]a=[1,2,3,4,5],我们对这个数组执行(3,5)(3,5)(3,5)操作,它就会变成a=[1,2]a=[1,2]a=[1,2]。此外,操作(2,4)(2,4)(2,4)的结果是a=[1,5]a=[1,5]a=[1,5],操作(1,3)(1,3)(1,3)的结果是a=[4,5]a=[4,5]a=[4,5]。
当aaa的长度大于kkk(即∣a∣>k|a|\gt k∣a∣>k)时,你必须重复操作。处理后数组aaa中所有剩余元素的最大可能中位数†^\dagger†是多少?
†^\dagger†长度为nnn的数组的中位数,就是我们对元素进行非递减排序后,索引为⌊(n+1)/2⌋\left \lfloor(n+1)/2\right\rfloor⌊(n+1)/2⌋的元素。例如median([2,1,5,4,3])=3median([2,1,5,4,3])=3median([2,1,5,4,3])=3、median([5])=5median([5])=5median([5])=5和median([6,8,2,4])=4median([6,8,2,4])=4median([6,8,2,4])=4。
分析:
一般涉及到中位数的题,很多都可以尝试二分来解。
本题我们考虑二分中位数,然后需要判定序列中只剩下0,10,10,1,能否进行一些操作使得剩下的数中111的数量严格大于000的数量。
令dpidp_idpi表示考虑序列前iii位,进行一些操作使得111的数量减去000的数量最大是多少,可以得到dpi=max(dpi−l×k−1)+validp_i=max(dp_{i−l×k−1})+val_idpi=max(dpi−l×k−1)+vali,这样删除了一个长度为l×kl×kl×k的段,如果删除的段相交,则可以合并成一个大段,因此在这个dpdpdp中认为删除的段不交是没有问题的。
然后记录余数优化,但我们注意到剩下的段长度至多为kkk这一限制没有被考虑,注意这里无需考虑长度非000的限制,因为长度为000的序列中111的数量减去000的数量等于000并不满足条件,考虑怎么处理长度至多为kkk的限制,即至多转移kkk次的限制,可以发现第iii次转移的点的下标对kkk取模后为iii,那么我们不让对kkk取模后为000的状态去转移其他状态即可。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const LL N = 5e5 + 10;
const LL MAXN = 1e9 + 10;
LL dp[N], cnt[N], n, k;
LL a[N];
bool check(LL x) {
if (k > n) {
LL c = 0;
for (LL i = 1; i <= n; i++)
c += (a[i] >= x ? 1 : -1);
return c > 0;
}
for (LL i = 1; i <= n; i++)
dp[i] = -INT_MAX;
for (LL i = 0; i < k; i++)
cnt[i] = -INT_MAX;
cnt[0] = 0;
LL g = -INT_MAX;
for (LL i = 1; i <= n; i++) {
if (i % k != 0) {
dp[i] = cnt[i % k - 1] + (a[i] >= x ? 1 : -1);
} else
dp[i] = cnt[k - 1] + (a[i] >= x ? 1 : -1);
if (i % k != 0)
cnt[i % k] = max(cnt[i % k], dp[i]);
if (i % k == 0)
g = max(g, dp[i]);
}
cnt[0] = max(cnt[0], g);
return max(dp[n], (n + 1) % k == 0 ? cnt[k - 1] : cnt[(n + 1) % k - 1]) > 0;
}
void solve() {
cin >> n >> k;
for (LL i = 1; i <= n; i++)
cin >> a[i];
LL l = 0, r = MAXN;
while (l + 1 < r) {
LL mid = (l + r) >> 1;
if (check(mid))
l = mid;
else
r = mid;
}
cout << l << endl;
}
int main() {
int T;
cin >> T;
while (T--) {
solve();
}
return 0;
}
E.Xor-Grid Problem(状压DP)
题意:
给定一个大小为n×mn \times mn×m的矩阵aaa,其中每个单元格都包含一个非负整数。位于矩阵第iii行与第jjj列交点上的整数叫做ai,ja_{i,j}ai,j。
让我们把f(i)f(i)f(i)和g(j)g(j)g(j)分别定义为iii(第iii行)和jjj(第jjj列)中所有整数的异或和。在一次操作中,你可以
- 选择任意一行iii,然后为每个1≤j≤m1\le j\le m1≤j≤m分配ai,j:=g(j)a_{i,j}:=g(j)ai,j:=g(j)
- 选择任意一列jjj,然后为每个1≤i≤n1\le i\le n1≤i≤n赋值ai,j:=f(i)a_{i,j}:=f(i)ai,j:=f(i)。

对矩阵的222列进行操作的示例。
在本例中,当我们对列222执行操作时,这一列中的所有元素都会发生变化:
- a1,2:=f(1)=a1,1⊕a1,2⊕a1,3⊕a1,4=1⊕1⊕1⊕1=0a_{1,2}:=f(1)=a_{1,1}\oplus a_{1,2}\oplus a_{1,3}\oplus a_{1,4}=1\oplus 1\oplus 1\oplus 1=0a1,2:=f(1)=a1,1⊕a1,2⊕a1,3⊕a1,4=1⊕1⊕1⊕1=0
- a2,2:=f(2)=a2,1⊕a2,2⊕a2,3⊕a2,4=2⊕3⊕5⊕7=3a_{2,2}:=f(2)=a_{2,1}\oplus a_{2,2}\oplus a_{2,3}\oplus a_{2,4}=2\oplus 3\oplus 5\oplus 7=3a2,2:=f(2)=a2,1⊕a2,2⊕a2,3⊕a2,4=2⊕3⊕5⊕7=3
- a3,2:=f(3)=a3,1⊕a3,2⊕a3,3⊕a3,4=2⊕0⊕3⊕0=1a_{3,2}:=f(3)=a_{3,1}\oplus a_{3,2}\oplus a_{3,3}\oplus a_{3,4}=2\oplus 0\oplus 3\oplus 0=1a3,2:=f(3)=a3,1⊕a3,2⊕a3,3⊕a3,4=2⊕0⊕3⊕0=1
- a4,2:=f(4)=a4,1⊕a4,2⊕a4,3⊕a4,4=10⊕11⊕12⊕16=29a_{4,2}:=f(4)=a_{4,1}\oplus a_{4,2}\oplus a_{4,3}\oplus a_{4,4}=10\oplus 11\oplus 12\oplus 16=29a4,2:=f(4)=a4,1⊕a4,2⊕a4,3⊕a4,4=10⊕11⊕12⊕16=29
可以多次进行上述运算。然后,我们将所有相邻单元格对之间的绝对差求和,计算出最终矩阵的beauty\textit{beauty}beauty。
更正式地说,如果所有单元格(x,y)(x,y)(x,y)和(r,c)(r,c)(r,c)相邻,则为beauty(a)=∑∣ax,y−ar,c∣\textit{beauty}(a)=\sum|a_{x,y}-a_{r,c}|beauty(a)=∑∣ax,y−ar,c∣。如果两个单元格共用一条边,则认为它们是相邻的。
求所有可得矩阵中最小的beauty\textit{beauty}beauty。
分析:
手动模拟一下可以发现对于不同的两行或列做三次操作相当于交换这两行或列。
将最终矩阵的美丽值分成行美丽值和列美丽值之和,不难发现行列的操作是独立的,也就是行交换不影响列美丽值,列交换同理。
因此我们就是要将行列重新排序,然后让行美丽值最大,列美丽值最大。
由于n,m≤15n,m≤15n,m≤15,可以用状压dpdpdp解决。但我们发现,我们可以在行列交换完后单独对某一行或列进行操作,这样这一行或列的值都会变成新的数。但因为这样的操作只可能在交换操作完后出现,且只对某一行或列操作,可以直接O(nm)O(nm)O(nm)枚举单独操作的行和列。
代码:
#include<bits/stdc++.h>
using namespace std;
vector<int> bl[1 << 15];
int n, m, a[20][20], b[20], c[20], X[20], Y[20];
int d[20][20], f[1 << 15][20];
inline int DP(int len) {
memset(f, 0x3f3f3f3f, sizeof(f));
for (int i = 0; i < len; ++i) f[1 << i][i] = 0;
for (int S = 1; S < (1 << len); ++S) {
if (bl[S].size() == 1) continue;
for (int i = 0; i < bl[S].size(); ++i) {
int p = bl[S][i];
int _S = S ^ (1 << p);
for (int j = 0; j < bl[_S].size(); ++j) {
int q(bl[_S][j]);
f[S][p] = min(f[S][p], f[_S][q] + d[p][q]);
}
}
}
int S = (1 << len) - 1;
int ret = 0x3f3f3f3f;
for (int i = 0; i < len; ++i)
ret = min(ret, f[S][i]);
return ret;
}
inline int solve() {
int ret = 0;
memset(d, 0, sizeof(d));
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
for (int k = 0; k < m; ++k)
d[i][j] += abs(a[i][k] - a[j][k]);
ret += DP(n);
memset(d, 0, sizeof(d));
for (int i = 0; i < m; ++i)
for (int j = 0; j < m; ++j)
for (int k = 0; k < n; ++k)
d[i][j] += abs(a[k][i] - a[k][j]);
ret += DP(m);
return ret;
}
int main() {
for (int S = 1; S < (1 << 15); ++S)
for (int i = 0; i < 15; ++i)
if (S >> i & 1)
bl[S].push_back(i);
int T;
cin >> T;
while (T--) {
cin >> n >> m;
int al = 0;
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j) {
cin >> a[i][j];
al ^= a[i][j];
}
for (int i = 0; i < n; ++i) {
X[i] = 0;
for (int j = 0; j < m; ++j)
X[i] ^= a[i][j];
}
for (int j = 0; j < m; ++j) {
Y[j] = 0;
for (int i = 0; i < n; ++i)
Y[j] ^= a[i][j];
}
int ans = solve();
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j)
b[j] = a[i][j];
for (int j = 0; j < m; ++j)
a[i][j] = Y[j];
ans = min(ans, solve());
for (int j = 0; j < m; ++j)
a[i][j] = b[j];
}
for (int j = 0; j < m; ++j) {
for (int i = 0; i < n; ++i)
c[i] = a[i][j];
for (int i = 0; i < n; ++i)
a[i][j] = X[i];
ans = min(ans, solve());
for (int i = 0; i < n; ++i)
a[i][j] = c[i];
}
for (int i = 0; i < n; ++i)
for (int j = 0; j < m; ++j) {
for (int _j = 0; _j < m; ++_j)
b[_j] = a[i][_j];
for (int _i = 0; _i < n; ++_i)
c[_i] = a[_i][j];
for (int _j = 0; _j < m; ++_j)
a[i][_j] = Y[_j];
for (int _i = 0; _i < n; ++_i)
a[_i][j] = X[_i];
a[i][j] = al;
ans = min(ans, solve());
for (int _j = 0; _j < m; ++_j)
a[i][_j] = b[_j];
for (int _i = 0; _i < n; ++_i)
a[_i][j] = c[_i];
}
cout << ans << endl;
}
return 0;
}
F1.Dyn-scripted Robot (Easy Version)(模拟)
题意:
已知在OxyOxyOxy平面上有一个w×hw \times hw×h矩形,矩形的左下方有点(0,0)(0,0)(0,0),右上方有点(w,h)(w,h)(w,h)。
还有一个最初位于点(0,0)(0,0)(0,0)的机器人和一个由nnn个字符组成的脚本sss。每个字符都是L、R、U或D,分别指示机器人向左、向右、向上或向下移动。
机器人只能在矩形内移动,否则将更改脚本sss如下:
- 如果试图向垂直边界外移动,则会将所有
L字符改为R字符(反之亦然,将所有R字符改为L字符)。 - 如果尝试向水平边界外移动,则会将所有
U字符更改为D字符(反之亦然,将所有D字符更改为U字符)。
然后,它会从无法执行的字符开始执行更改后的脚本。

机器人移动过程的示例,s="ULULURD"s=\text{"ULULURD"}s="ULULURD"。
脚本sss将被连续执行kkk次。即使重复执行,字符串sss的所有变化都将被保留。在此过程中,机器人总共会移动到(0,0)(0,0)(0,0)点多少次?请注意,初始位置不计算在内。
分析:
参考样例进行模拟可以发现,每2w2w2w行2h2h2h列即为一个循环。因此使用map记录出第一次操作每一个位置走到过的次数。然后考虑枚举kkk次不同的走法。每一次直接使用公式计算出其所对应的起点并更新答案即可。
设第一次结束之后位于坐标(x,y)(x,y)(x,y),那么第iii轮的起点即为(−(i−1)xmod 2w,−(i−1)ymod 2h)(-(i-1)x \mod 2w,-(i-1)y \mod 2h)(−(i−1)xmod2w,−(i−1)ymod2h)。
代码:
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
int main() {
int T;
cin >> T;
while (T--) {
LL n, k, w, h;
cin >> n >> k >> w >> h;
string s;
cin >> s;
map<pair<LL, LL>, LL> mp;
LL x = 0, y = 0;
for (LL i = 0; i < n; ++i) {
if (s[i] == 'L')
--x;
else if (s[i] == 'R')
++x;
else if (s[i] == 'U')
++y;
else
--y;
x = (x + w + w) % (w + w);
y = (y + h + h) % (h + h);
++mp[{x, y}];
}
LL res = 0;
for (LL i = 0; i < k; ++i) {
LL _x = (-i * x % (w + w) + (w + w)) % (w + w);
LL _y = (-i * y % (h + h) + (h + h)) % (h + h);
res += mp[{_x, _y}];
}
cout << res << endl;
}
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

787

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



