A.Question Marks(思维)
题意:
蒂姆正在做一个由 4 n 4n 4n个问题组成的测试;每个问题有 4 4 4个选项:“A”、“B”、“C"和"D”。每个选项都有 n n n个正确答案,也就是说有 n n n道题的答案是"A", n n n道题的答案是"B", n n n道题的答案是"C", n n n道题的答案是"D"。
对于每道题,蒂姆都会把答案写在答题纸上。如果想不出答案,他会在该题上留下问号?
他的答题纸有 4 n 4n 4n个字符。蒂姆最多能答对多少题?
分析:
本题我们使用map
分别记录ABCD
四个字符出现的次数,和
n
n
n比较取较小值累加一下即可。
代码:
#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(贪心)
题意:
给定一个由 n n n个正整数组成的数组 a a a。
在一次操作中,你可以选取任意一对索引 ( i , j ) (i,j) (i,j),使得 a i a_i ai和 a j a_j aj具有不同的奇偶性,然后用它们的和替换较小的索引。更正式的说法是
- 如果是 a i < a j a_i\lt a_j ai<aj,则用 a i + a j a_i+a_j ai+aj替换 a i a_i ai;
- 否则,用 a i + a j a_i+a_j ai+aj替换 a j a_j aj。
求使数组中所有元素具有相同奇偶性所需的最少操作数。
分析:
由于奇数加偶数等于奇数,所以除了一开始全为偶数的情况,最后的结果必定是全为奇数。贪心的想,设 x x x为当前最大的奇数,则对于一个偶数 a i a_i ai必定与 x x x进行操作。若 a i < x a_i\lt x ai<x,则进行一次操作。由于每个偶数至少操作一次,这样肯定不劣。我们想让这样的操作尽可能多,对 a a a从小到大排序,依次处理。这样 x x x会随着操作越来越大。
对于剩余的 a i > x a_i>x ai>x,发现只要进行两次操作就可以把其变为奇数。可以想到拉出剩余的最大的 a i a_i ai进行操作,这样就可以尽可能增大 x x x,使得其他小于 a i a_i ai的数只需一次操作。
代码:
#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(模拟)
题意:
有一间公寓由 n n n个房间组成,每个房间的灯最初都是关着的。
为了控制这些房间的灯光,公寓的主人决定在房间里安装芯片,这样每个房间正好有一个芯片,芯片安装的时间各不相同。具体来说,这些时间由数组 a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1,a2,…,an表示,其中 a i a_i ai是在第 i i i个房间安装芯片的时间(以分钟为单位)。
芯片一经安装,就会每隔 k k k分钟改变房间的灯光状态–在 k k k分钟内开灯,然后在接下来的 k k k分钟内关灯,再在接下来的 k k k分钟内开灯,以此类推。换句话说,芯片在 a i a_i ai、 a i + k a_i+k ai+k、 a i + 2 k a_i+2k ai+2k、 a i + 3 k a_i+3k ai+3k、 … \ldots …分钟时改变第 i i i个房间的灯光状态。
公寓里所有房间最早亮灯的时刻是什么时候?
分析:
读题发现,灯的开关存在循环,最终的答案一定会在最后一个亮起的灯亮起的时间里。
如果我们用 t t t表示最后一个给房间安装芯片的时刻,那么答案一定在 t t t至 t + k − 1 t+k−1 t+k−1之间。因此,我们定义两个指针 l l l和 r r r并遍历所有的 a i a_i ai,计算当前灯在 t t t至 t + k − 1 t+k−1 t+k−1之间的亮起时间,同时更新 l l l和 r r r。
最后,如果 l ≤ r l≤r l≤r,则存在所有的灯都同时亮起的时刻,因此输出 l l l,否则不存在这样的时刻,输出 − 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(动态规划)
题意:
给定两个正整数 n n n和 k k k以及另一个由 n n n个整数组成的数组 a a a。
在一次操作中,可以选择 a a a中任意一个大小为 k k k的子数组,然后将其从数组中删除,而不改变其他元素的顺序。更正式地说,假设 ( l , r ) (l,r) (l,r)是对子数组 a l , a l + 1 , … , a r a_l,a_{l+1},\ldots,a_r al,al+1,…,ar的操作,使得 r − l + 1 = k r-l+1=k r−l+1=k,那么执行此操作意味着将 a a a替换为 [ a 1 , … , a l − 1 , a r + 1 , … , a n ] [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]。
当 a a a的长度大于 k k k(即 ∣ a ∣ > k |a|\gt k ∣a∣>k)时,你必须重复操作。处理后数组 a a a中所有剩余元素的最大可能中位数 † ^\dagger †是多少?
† ^\dagger †长度为 n n n的数组的中位数,就是我们对元素进行非递减排序后,索引为 ⌊ ( n + 1 ) / 2 ⌋ \left \lfloor(n+1)/2\right\rfloor ⌊(n+1)/2⌋的元素。例如 m e d i a n ( [ 2 , 1 , 5 , 4 , 3 ] ) = 3 median([2,1,5,4,3])=3 median([2,1,5,4,3])=3、 m e d i a n ( [ 5 ] ) = 5 median([5])=5 median([5])=5和 m e d i a n ( [ 6 , 8 , 2 , 4 ] ) = 4 median([6,8,2,4])=4 median([6,8,2,4])=4。
分析:
一般涉及到中位数的题,很多都可以尝试二分来解。
本题我们考虑二分中位数,然后需要判定序列中只剩下 0 , 1 0,1 0,1,能否进行一些操作使得剩下的数中 1 1 1的数量严格大于 0 0 0的数量。
令 d p i dp_i dpi表示考虑序列前 i i i位,进行一些操作使得 1 1 1的数量减去 0 0 0的数量最大是多少,可以得到 d p i = m a x ( d p i − l × k − 1 ) + v a l i dp_i=max(dp_{i−l×k−1})+val_i dpi=max(dpi−l×k−1)+vali,这样删除了一个长度为 l × k l×k l×k的段,如果删除的段相交,则可以合并成一个大段,因此在这个 d p dp dp中认为删除的段不交是没有问题的。
然后记录余数优化,但我们注意到剩下的段长度至多为 k k k这一限制没有被考虑,注意这里无需考虑长度非 0 0 0的限制,因为长度为 0 0 0的序列中 1 1 1的数量减去 0 0 0的数量等于 0 0 0并不满足条件,考虑怎么处理长度至多为 k k k的限制,即至多转移 k k k次的限制,可以发现第 i i i次转移的点的下标对 k k k取模后为 i i i,那么我们不让对 k k k取模后为 0 0 0的状态去转移其他状态即可。
代码:
#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 × m n \times m n×m的矩阵 a a a,其中每个单元格都包含一个非负整数。位于矩阵第 i i i行与第 j j j列交点上的整数叫做 a i , j a_{i,j} ai,j。
让我们把 f ( i ) f(i) f(i)和 g ( j ) g(j) g(j)分别定义为 i i i(第 i i i行)和 j j j(第 j j j列)中所有整数的异或和。在一次操作中,你可以
- 选择任意一行 i i i,然后为每个 1 ≤ j ≤ m 1\le j\le m 1≤j≤m分配 a i , j : = g ( j ) a_{i,j}:=g(j) ai,j:=g(j)
- 选择任意一列 j j j,然后为每个 1 ≤ i ≤ n 1\le i\le n 1≤i≤n赋值 a i , j : = f ( i ) a_{i,j}:=f(i) ai,j:=f(i)。
对矩阵的 2 2 2列进行操作的示例。
在本例中,当我们对列 2 2 2执行操作时,这一列中的所有元素都会发生变化:
- a 1 , 2 : = f ( 1 ) = a 1 , 1 ⊕ a 1 , 2 ⊕ a 1 , 3 ⊕ a 1 , 4 = 1 ⊕ 1 ⊕ 1 ⊕ 1 = 0 a_{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=0 a1,2:=f(1)=a1,1⊕a1,2⊕a1,3⊕a1,4=1⊕1⊕1⊕1=0
- a 2 , 2 : = f ( 2 ) = a 2 , 1 ⊕ a 2 , 2 ⊕ a 2 , 3 ⊕ a 2 , 4 = 2 ⊕ 3 ⊕ 5 ⊕ 7 = 3 a_{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=3 a2,2:=f(2)=a2,1⊕a2,2⊕a2,3⊕a2,4=2⊕3⊕5⊕7=3
- a 3 , 2 : = f ( 3 ) = a 3 , 1 ⊕ a 3 , 2 ⊕ a 3 , 3 ⊕ a 3 , 4 = 2 ⊕ 0 ⊕ 3 ⊕ 0 = 1 a_{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=1 a3,2:=f(3)=a3,1⊕a3,2⊕a3,3⊕a3,4=2⊕0⊕3⊕0=1
- a 4 , 2 : = f ( 4 ) = a 4 , 1 ⊕ a 4 , 2 ⊕ a 4 , 3 ⊕ a 4 , 4 = 10 ⊕ 11 ⊕ 12 ⊕ 16 = 29 a_{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=29 a4,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 ) = ∑ ∣ a x , y − a r , c ∣ \textit{beauty}(a)=\sum|a_{x,y}-a_{r,c}| beauty(a)=∑∣ax,y−ar,c∣。如果两个单元格共用一条边,则认为它们是相邻的。
求所有可得矩阵中最小的 beauty \textit{beauty} beauty。
分析:
手动模拟一下可以发现对于不同的两行或列做三次操作相当于交换这两行或列。
将最终矩阵的美丽值分成行美丽值和列美丽值之和,不难发现行列的操作是独立的,也就是行交换不影响列美丽值,列交换同理。
因此我们就是要将行列重新排序,然后让行美丽值最大,列美丽值最大。
由于 n , m ≤ 15 n,m≤15 n,m≤15,可以用状压 d p dp dp解决。但我们发现,我们可以在行列交换完后单独对某一行或列进行操作,这样这一行或列的值都会变成新的数。但因为这样的操作只可能在交换操作完后出现,且只对某一行或列操作,可以直接 O ( n m ) 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)(模拟)
题意:
已知在 O x y Oxy Oxy平面上有一个 w × h w \times h w×h矩形,矩形的左下方有点 ( 0 , 0 ) (0,0) (0,0),右上方有点 ( w , h ) (w,h) (w,h)。
还有一个最初位于点
(
0
,
0
)
(0,0)
(0,0)的机器人和一个由
n
n
n个字符组成的脚本
s
s
s。每个字符都是L
、R
、U
或D
,分别指示机器人向左、向右、向上或向下移动。
机器人只能在矩形内移动,否则将更改脚本 s s s如下:
- 如果试图向垂直边界外移动,则会将所有
L
字符改为R
字符(反之亦然,将所有R
字符改为L
字符)。 - 如果尝试向水平边界外移动,则会将所有
U
字符更改为D
字符(反之亦然,将所有D
字符更改为U
字符)。
然后,它会从无法执行的字符开始执行更改后的脚本。
机器人移动过程的示例, s = "ULULURD" s=\text{"ULULURD"} s="ULULURD"。
脚本 s s s将被连续执行 k k k次。即使重复执行,字符串 s s s的所有变化都将被保留。在此过程中,机器人总共会移动到 ( 0 , 0 ) (0,0) (0,0)点多少次?请注意,初始位置不计算在内。
分析:
参考样例进行模拟可以发现,每
2
w
2w
2w行
2
h
2h
2h列即为一个循环。因此使用map
记录出第一次操作每一个位置走到过的次数。然后考虑枚举
k
k
k次不同的走法。每一次直接使用公式计算出其所对应的起点并更新答案即可。
设第一次结束之后位于坐标 ( x , y ) (x,y) (x,y),那么第 i i i轮的起点即为 ( − ( i − 1 ) x m o d 2 w , − ( i − 1 ) y m o d 2 h ) (-(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,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。