ABC396题解
AtCoder Beginner Contest 396
A
算法标签: 模拟
问题陈述
给你一个长度为 NNN 的整数序列:A=(A1,A2,…,AN)A = (A_1, A_2, \ldots, A_N)A=(A1,A2,…,AN)。
请判断在 AAA 中是否有相同元素连续出现三次或三次以上的位置。
更正式地说,请判断是否存在一个整数 iii 与 1≤i≤N−21 \le i \le N-21≤i≤N−2 中的 Ai=Ai+1=Ai+2A_i = A_{i+1} = A_{i+2}Ai=Ai+1=Ai+2。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 110;
int n, w[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n;
for (int i = 0; i < n; ++i) cin >> w[i];
int i = 0, j = 2;
while (j < n) {
if (w[i] == w[j] && w[i] == w[i + 1]) {
cout << "Yes" << "\n";
return 0;
}
i++, j++;
}
cout << "No" << "\n";
return 0;
}
B
算法标签: 栈, 模拟
问题陈述
有一叠 100100100 纸牌,每张都标有整数 000 。
处理 QQQ 个查询。每个查询都属于以下情况之一:
- 输入 111 :将一张标有整数 xxx 的卡片放在堆栈顶部。
- 类型 222 :取出堆栈顶端的卡片,并输出写在该卡片上的整数。在此问题的限制条件下,牌堆中总是至少有一张牌。
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 210;
int n;
int stack[N], top;
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
top = 99;
cin >> n;
while (n--) {
int op, val;
cin >> op;
if (op == 1) {
cin >> val;
stack[++top] = val;
}
else cout << stack[top--] << "\n";
}
return 0;
}
C
算法标签: 前缀和, 贪心
问题陈述
有 NNN 个黑球和 MMM 个白球。
每个球都有一个值。第 iii 个黑球( 1≤i≤N1 \le i \le N1≤i≤N )的值是 BiB_iBi ,第 jjj 个白球( 1≤j≤M1 \le j \le M1≤j≤M )的值是 WjW_jWj 。
选择零个或更多的球,使得所选黑球的数量至少等于所选白球的数量。在所有这样的选择中,求所选球的数值之和的最大值。
思路
很容易想到时间复杂度O(n2)O(n ^ 2)O(n2)的做法, 但是球的数量10510 ^ 5105级别, 会超时, 因此需要优化到O(n2)O(n ^ 2)O(n2)以下
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
int n, m;
int b[N], w[N], gre;
LL s1[N], s2[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1; i <= n; ++i) cin >> b[i];
for (int i = 1; i <= m; ++i) cin >> w[i];
sort(b + 1, b + n + 1);
reverse(b + 1, b + n + 1);
sort(w + 1, w + m + 1);
reverse(w + 1, w + m + 1);
for (int i = 1; i <= n; ++i) {
if (b[i] > 0) gre = i;
s1[i] = (LL) s1[i - 1] + b[i];
}
for (int i = 1; i <= m; ++i) s2[i] = (LL) s2[i - 1] + w[i];
LL res = 0;
// 枚举选择白球的数量
for (int k = 0; k <= min(n, m); ++k) {
int cnt = max(k, gre);
res = max(res, s2[k] + s1[cnt]);
}
cout << res << "\n";
return 0;
}
D
算法标签: dfsdfsdfs
问题陈述
给你一个简单相连的无向图,图中有 NNN 个顶点,编号为 111 到 NNN,有 MMM 条边,编号为 111 到 MMM。边 iii 连接顶点 uiu_iui 和 viv_ivi,其标签为 wiw_iwi。
在从顶点 111 到顶点 NNN 的所有简单路径(不经过同一顶点多次的路径)中,求路径上边的标签的最小 XOR。
关于 XOR 的说明
对于非负整数 AAA 和 BBB,它们的 XOR A⊕BA \oplus BA⊕B 定义如下:
- 在 A⊕BA \oplus BA⊕B 的二进制表示中,当且仅当 AAA 和 BBB 的相同数位中恰好有一位是 111 时,2k (k≥0)2^k \ (k \ge 0)2k (k≥0) 对应数位上的数字是 111;否则,它是 000。
例如,3⊕5=63 \oplus 5 = 63⊕5=6(二进制为:011⊕101=110011 \oplus 101 = 110011⊕101=110)。
一般来说,kkk 个整数 p1,…,pkp_1, \dots, p_kp1,…,pk 的 XOR 定义为 (⋯((p1⊕p2)⊕p3)⊕⋯⊕pk)(\cdots ((p_1 \oplus p_2) \oplus p_3) \oplus \cdots \oplus p_k)(⋯((p1⊕p2)⊕p3)⊕⋯⊕pk)。可以证明它与 p1,…,pkp_1, \dots, p_kp1,…,pk 的顺序无关。
思路
注意到, 点的数量不大于101010, 直接暴搜
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 12, M = N * N;
int n, m;
int head[N], edge_end[M], next_edge[M], edge_index;
LL w[M];
bool vis[N];
LL res = 9e18;
void add(int ver1, int ver2, LL val) {
edge_end[edge_index] = ver2, next_edge[edge_index] = head[ver1], w[edge_index] = val, head[ver1] = edge_index++;
}
void dfs(int u, LL curr) {
if (u == n) {
res = min(res, curr);
return;
}
for (int i = head[u]; ~i; i = next_edge[i]) {
int ver = edge_end[i];
if (!vis[ver]) {
vis[ver] = true;
dfs(ver, curr ^ w[i]);
vis[ver] = false;
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
memset(head, -1, sizeof head);
cin >> n >> m;
while (m--) {
int u, v;
LL val;
cin >> u >> v >> val;
add(u, v, val);
add(v, u, val);
}
vis[1] = true;
dfs(1, 0);
cout << res << "\n";
return 0;
}
E
算法标签: 贪心, dfsdfsdfs
问题陈述
给你整数 N,MN, MN,M 和长度为 MMM 的三个整数序列:X=(X1,X2,…,XM)X = (X_1, X_2, \ldots, X_M)X=(X1,X2,…,XM)、Y=(Y1,Y2,…,YM)Y = (Y_1, Y_2, \ldots, Y_M)Y=(Y1,Y2,…,YM) 和 Z=(Z1,Z2,…,ZM)Z = (Z_1, Z_2, \ldots, Z_M)Z=(Z1,Z2,…,ZM)。保证 XXX 和 YYY 中的所有元素都在 111 和 NNN 之间。
我们称长度为 NNN 的非负整数序列 A=(A1,A2,…,AN)A = (A_1, A_2, \ldots, A_N)A=(A1,A2,…,AN) 为好序列,当且仅当它满足以下条件时:
- 对于每一个有 1≤i≤M1 \le i \le M1≤i≤M 的整数 iii,AXiA_{X_i}AXi 与 AYiA_{Y_i}AYi 的 XOR 为 ZiZ_iZi。
判断是否存在一个好序列 A=(A1,A2,…,AN)A=(A_1,A_2,\ldots,A_N)A=(A1,A2,…,AN),如果存在,找出一个使其元素之和 ∑i=1NAi\displaystyle \sum_{i=1}^N A_ii=1∑NAi 最小的好序列。
关于 XOR 的说明
对于非负整数 AAA 和 BBB,它们的 XOR A⊕BA \oplus BA⊕B 定义如下:
- 在 A⊕BA \oplus BA⊕B 的二进制表示中,当且仅当 AAA 和 BBB 的相同数位中恰好有一位是 111 时,2k (k≥0)2^k \ (k \ge 0)2k (k≥0) 对应数位上的数字是 111;否则,它是 000。
例如,3⊕5=63 \oplus 5 = 63⊕5=6(二进制为:011⊕101=110011 \oplus 101 = 110011⊕101=110)。
思路
由题意, 分为两步
- 是否存在该序列?
- 如何最小化AiA_iAi
首先判断是否无解
AXi⊕AYi=ZiA_{X_i} \oplus A_{Y_i} = Z_iAXi⊕AYi=Zi, 对于异或操作有以下性质
a⊕b=c,a⊕c=b,b⊕c=aa \oplus b = c, a \oplus c = b, b \oplus c = aa⊕b=c,a⊕c=b,b⊕c=a, 异或是可逆的
因为Xi,Yi,ZiX_i,Y_i, Z_iXi,Yi,Zi是确定的, AXiA_{X_i}AXi和AYiA_{Y_i}AYi只要确定一个数, 就可以确定另外一个数
会形成如图的连通块, 在连通块中知道确定一个数, 整个连通块的数都会被确定
如果从uuu到vvv出现了两条不一样的路径, 出现矛盾
如何找这些环的边?
维护并查集, 检查Z=Spath1⊕Spath2Z = S_{path_1} \oplus S_{path_2}Z=Spath1⊕Spath2, 如果不相等无解
如何维护最小值?
在连通块中确定任何一个元素就能确定任意一个元素, 考虑树的根节点
因为异或操作是逐位异或的, 因此位与位之间是独立的, 每一位单独考虑, 因为要求和最小, 因此要求111的数量是最小的
算法流程
- 将图构建成生成树, 检查非树边
- 求最小值, 逐位考虑, 看根节点是选000还是选111, 最后知道了根节点的值, 然后整个树的值就知道了
XOR 的性质
XOR 操作具有以下性质:
- 自反性:
A⊕A=0 A \oplus A = 0 A⊕A=0 - 结合律:
(A⊕B)⊕C=A⊕(B⊕C) (A \oplus B) \oplus C = A \oplus (B \oplus C) (A⊕B)⊕C=A⊕(B⊕C) - 交换律:
A⊕B=B⊕A A \oplus B = B \oplus A A⊕B=B⊕A
由于这些性质,调整根节点的值 A[root]A[root]A[root] 为 A[root]⊕offA[root] \oplus offA[root]⊕off 会使得所有顶点的值 A[x]A[x]A[x] 变为 A[x]⊕offA[x] \oplus offA[x]⊕off。
一致性调整
假设根节点的值被调整为 A[root]⊕offA[root] \oplus offA[root]⊕off,那么对于任意顶点 xxx,其值 A[x]A[x]A[x] 会被调整为:
A[x]⊕off=(A[root]⊕path_xor)⊕off=(A[root]⊕off)⊕path_xor A[x] \oplus off = (A[root] \oplus \text{path\_xor}) \oplus off = (A[root] \oplus off) \oplus \text{path\_xor} A[x]⊕off=(A[root]⊕path_xor)⊕off=(A[root]⊕off)⊕path_xor
其中,path_xor\text{path\_xor}path_xor 是从根节点到顶点 xxx 的路径上所有边的 XOR 值
这种调整方式确保了所有顶点的值仍然满足边的 XOR 条件
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10, M = 2e5 + 10;
int n, m;
int head[N], edge_end[M], next_edge[M], w[M], edge_index;
int res[N];
void add(int ver1, int ver2, LL val) {
edge_end[edge_index] = ver2, next_edge[edge_index] = head[ver1], w[edge_index] = val, head[ver1] = edge_index++;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
memset(head, -1, sizeof head);
cin >> n >> m;
for (int i = 0; i < m; ++i) {
int u, v, w;
cin >> u >> v >> w;
u--, v--;
add(u, v, w);
add(v, u, w);
}
memset(res, -1, sizeof res);
for (int x = 0; x < n; ++x) {
if (res[x] != -1) continue;
vector<int> q;
q.push_back(x);
res[x] = 0;
int cnt[30]{0};
for (int i = 0; i < q.size(); ++i) {
int u = q[i];
for (int j = 0; j < 30; ++j) cnt[j] += res[u] >> j & 1;
for (int j = head[u]; ~j; j = next_edge[j]) {
int ver = edge_end[j];
if (res[ver] == -1) {
res[ver] = res[u] ^ w[j];
q.push_back(ver);
}
else if (res[ver] != (res[u] ^ w[j])) {
cout << -1 << "\n";
return 0;
}
}
}
int off = 0;
for (int j = 0; j < 30; ++j) {
if (cnt[j] > q.size() - cnt[j]) {
off ^= 1 << j;
}
}
for (int ver : q) res[ver] ^= off;
}
for (int i = 0; i < n; ++i) cout << res[i] << " ";
cout << "\n";
return 0;
}
F
算法标签: 分治, 树状数组, Fenwick−TreeFenwick-TreeFenwick−Tree
问题陈述
给你一个整数 N,MN, MN,M 和一个长度为 NNN 的非负整数序列 A=(A1,A2,…,AN)A = (A_1, A_2, \ldots, A_N)A=(A1,A2,…,AN)。
就 k=0,1,…,M−1k = 0, 1, \ldots, M-1k=0,1,…,M−1 求解下面的问题:
定义一个整数序列 B=(B1,B2,…,BN)B = (B_1, B_2, \ldots, B_N)B=(B1,B2,…,BN),使 BiB_iBi 除以 MMM 后的余数是 Ai+kA_i + kAi+k。求 BBB 中的倒数。
什么是倒数?
序列 (A1,A2,…,AN)(A_1, A_2, \dots, A_N)(A1,A2,…,AN) 的反转数是满足 1≤i<j≤N1 \le i < j \le N1≤i<j≤N 和 Ai>AjA_i > A_jAi>Aj 的整数对 (i,j)(i, j)(i,j) 的个数。
思路
Bi=(Ai+k)mod MB_i = (A_i + k) \mod MBi=(Ai+k)modM. 求BBB的逆序对数量, 首先考虑简单情况, 如果没有mod M\mod MmodM, 输出就是原AiA_iAi的逆序对数量
考虑简单情况, Bi=Ai+1B_i = A_i + 1Bi=Ai+1, 哪些逆序对数量发生改变?
Bi=M−1B_i= M - 1Bi=M−1的那些数如果加111, mod M\mod MmodM变为000, 逆序对数量发生变化, 而对于0≤Bi≤M−20 \le B_i \le M - 20≤Bi≤M−2的数字, 加上111再对MMM取模逆序对数量不会发生变化
因此考虑Bi=M−1B_i = M - 1Bi=M−1的情况
会与每一个后面的非M−1M - 1M−1的位置形成逆序对, 因为在mod M\mod MmodM情况下, M−1M - 1M−1就是最大值
逆序对的变化量Δcnt=−[M−1]+[0]\Delta cnt = -[M - 1] + [0]Δcnt=−[M−1]+[0], 也就是减去变化前M−1M - 1M−1位置的逆序对数量, 加上变为000后与前面形成的逆序对数量
假设当前位置是ppp, 减去的逆序对数量等于n−p−cntM−1n - p - cnt_{M - 1}n−p−cntM−1
因为每个数加111, 因此变为000后前面与前面形成的逆序对数量等于p−1p - 1p−1
以上是k=1k = 1k=1的逆序对数量, 以此类推计算到到k=M−1k = M - 1k=M−1, 也就是mod M\mod MmodM的最大余数
第iii个数什么时候变成MMM?
在M−AiM - A_iM−Ai时刻mod M\mod MmodM变为000, 算法时间复杂度O(nlogn)O(n\log n)O(nlogn)
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
int n, m;
int w[N], tr[N];
vector<int> change[N];
int lowbit(int val) {
return val & -val;
}
void add(int u, int val){
for (int i = u; i <= m; i += lowbit(i)) tr[i] += val;
}
int query(int u) {
int res = 0;
for (int i = u; i; i -= lowbit(i)) res += tr[i];
return res;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m;
LL res = 0;
for (int i = 1; i <= n; ++i) {
cin >> w[i];
change[m - w[i]].push_back(i);
res += query(m - w[i] - 1);
add(m - w[i], 1);
}
cout << res << "\n";
for (int k = 1; k < m; ++k) {
// 枚举在k时刻从m - 1变为0的所有位置
for (int j = 0, sz = change[k].size(); j < sz; ++j) {
int p = change[k][j];
res -= n - p - (sz - j - 1);
res += p - 1 - j;
}
cout << res << "\n";
}
return 0;
}
G
算法标签: FWTFWTFWT, 状态压缩dpdpdp
问题陈述
有一个 H×WH \times WH×W 网格,每个单元格都包含 000 或 111。从顶部起第 iii 行和从左侧起第 jjj 列的单元格中包含一个整数 Ai,jA_{i,j}Ai,j。
您可以按照任意顺序执行以下两种操作任意多次:
- 操作 X:选择一个整数 xxx(1≤x≤H1 \leq x \leq H1≤x≤H)。对于每一个整数 1≤y≤W1 \leq y \leq W1≤y≤W,将 Ax,yA_{x,y}Ax,y 替换为 1−Ax,y1 - A_{x,y}1−Ax,y。
- 操作 Y:选择一个整数 yyy(1≤y≤W1 \leq y \leq W1≤y≤W)。对于每一个整数 1≤x≤H1 \leq x \leq H1≤x≤H,将 Ax,yA_{x,y}Ax,y 替换为 1−Ax,y1 - A_{x,y}1−Ax,y。
求替换后 ∑x=1H∑y=1WAx,y\displaystyle \sum_{x=1}^H \sum_{y=1}^W A_{x,y}x=1∑Hy=1∑WAx,y 的最小值。
思路
列的全部情况是O(218)O(2 ^ {18})O(218), 然后枚举行, 但是时间复杂度来到了O(H×2W)O(H\times 2 ^{W})O(H×2W), 因为HHH的范围是2×1052 \times 10 ^ 52×105, 会超时
考虑每一列的操作数是XXX, 设每一行的000的数量是c0c_0c0, 111的数量是c1c_1c1, 当c0>c1c_0 > c_1c0>c1的时候这一行不需要翻转
c0≤c1c_0 \le c_1c0≤c1的情况下, 翻转取得的结果会变好, 因此这一行最小的111的数量就是min(c1,m−c1)\min (c1, m - c1)min(c1,m−c1)
设每一行111的数量是pip_ipi, 那么总的111的数量的最小值就是
min0≤X<2W{p(Bi⊕X),W−p(Bi⊕X)} \min _{ 0 \le X < 2 ^ W} \left \{ p(B_i \oplus X), W - p(B_i \oplus X) \right \} 0≤X<2Wmin{p(Bi⊕X),W−p(Bi⊕X)}
动态规划: f[X][j][c]f[X][j][c]f[X][j][c], 代表考虑前jjj列, 并且Bi⊕XB_i \oplus XBi⊕X与XXX在ccc位上不同的行的数量, 换句话说, 就是列的操作数是XXX的情况下, 需要翻转的行的数量
因为每一行的最小值可以确定, 因此答案可以转化为
∑c=0Wdp[X][W][c]×min(c,W−c). \sum_{c=0}^W dp[X][W][c] \times \min(c, W - c). c=0∑Wdp[X][W][c]×min(c,W−c).
时间复杂度O(W2×2W)O(W ^ 2\times 2 ^ W)O(W2×2W)
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 2e5 + 10, INF = 0x3f3f3f3f;
int r, c;
int row[N];
int f[20][20][1 << 20];
int ans = INF;
char str[20];
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> r >> c;
for (int i = 1; i <= r; ++i) {
cin >> str;
for (int j = 0; j < c; ++j) {
row[i] = row[i] << 1 | (str[j] - '0');
}
f[0][0][row[i]] += 1;
}
for (int i = 1; i <= c; ++i) {
for (int j = 0; j <= c; ++j) {
// 枚举对列的所有操作X
for (int k = 0; k < 1 << c; ++k) {
// 不翻转第i列
f[i][j][k] += f[i - 1][j][k];
// 翻转第i列
f[i][j + 1][k] += f[i - 1][j][k ^ (1 << (i - 1))];
}
}
}
for (int k = 0; k < 1 << c; ++k) {
int sum = 0;
// 枚举翻转的列数
for (int j = 0; j <= c; ++j) {
sum += f[c][j][k] * min(j, c - j);
}
ans = min(ans, sum);
}
cout << ans << "\n";
return 0;
}
更多推荐
所有评论(0)