比赛链接
官方题解
Problem A. Sign Flipping
将奇数位的数取非正值,偶数位的数取非负值即可。
单组数据时间复杂度 O ( N ) O(N) O(N) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int main() {
int T; read(T);
while (T--) {
int n; read(n);
for (int i = 1; i <= n; i++) {
int x; read(x);
if (i & 1) printf("%d ", -abs(x));
else printf("%d ", abs(x));
}
printf("\n");
}
return 0;
}
Problem B. Neighbor Grid
若一个格子上初始时的数大于与其相邻的格子数,则答案显然为 No 。
否则,将所有格子上的数增加至与其相邻的格子数一定会形成一组合法解。
单组数据时间复杂度 O ( N × M ) O(N\times M) O(N×M) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 305;
const int dx[4] = {0, 0, 1, -1};
const int dy[4] = {1, -1, 0, 0};
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, m, a[MAXN][MAXN], d[MAXN][MAXN];
int main() {
int T; read(T);
while (T--) {
read(n), read(m);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
read(a[i][j]), d[i][j] = 0;
for (int i = 1; i <= n - 1; i++)
for (int j = 1; j <= m; j++)
d[i][j]++, d[i + 1][j]++;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m - 1; j++)
d[i][j]++, d[i][j + 1]++;
bool ans = true;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (a[i][j] > d[i][j]) ans = false;
if (ans) {
puts("YES");
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++)
printf("%d ", d[i][j]);
printf("\n");
}
} else puts("NO");
}
return 0;
}
Problem C. Element Extermination
答案为 Yes 当且仅当 a 1 ≤ a N a_1\leq a_N a1≤aN 。
考虑其必要性,若 a 1 > a N a_1>a_N a1>aN ,则无论如何操作,序列中第一个元素将始终大于最后一个元素,从而不可能使序列的长度减少至 2 2 2 以下。考虑其充分性,则在保证 a 1 ≤ a N a_1\leq a_N a1≤aN 的情况下任意操作即可构造出一组消除方案,由于 a 1 ≤ a N a_1\leq a_N a1≤aN ,序列长度 ≥ 2 \geq2 ≥2 的时候,一定存在可以操作的位置。
单组数据时间复杂度 O ( N ) O(N) O(N) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int main() {
int T; read(T);
while (T--) {
int n; read(n);
int x = 0, y = 0;
for (int i = 1; i <= n; i++) {
int z; read(z);
if (i == 1) x = z;
else y = z;
}
if (x < y) puts("YES");
else puts("NO");
}
return 0;
}
Problem D. Replace by MEX
考虑通过操作序列使得 a i = i − 1 a_i=i-1 ai=i−1 。
令序列当前的 Mex 为
x
(
x
≤
N
)
x\ (x\leq N)
x (x≤N) ,显然,序列中此时不存在元素
x
x
x 。
若
x
<
N
x<N
x<N ,则说明
a
x
+
1
≠
x
a_{x+1}\ne x
ax+1=x ,令
a
x
+
1
=
x
a_{x+1}=x
ax+1=x 即可。
若
x
=
N
x=N
x=N ,则任意操作一个尚不满足
a
i
=
i
−
1
a_i=i-1
ai=i−1 的位置。
单组数据时间复杂度 O ( N 2 ) O(N^2) O(N2) ,操作次数不超过 2 N 2N 2N 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, a[MAXN];
int cur() {
static int vis[MAXN], tag = 0; tag++;
for (int i = 1; i <= n; i++)
vis[a[i]] = tag;
int ans = 0;
while (vis[ans] == tag) ans++;
return ans;
}
bool check() {
for (int i = 2; i <= n; i++)
if (a[i - 1] > a[i]) return false;
return true;
}
int main() {
int T; read(T);
while (T--) {
read(n);
for (int i = 1; i <= n; i++)
read(a[i]);
vector <int> ans;
while (!check()) {
int val = cur();
if (val != n) {
a[val + 1] = val;
ans.push_back(val + 1);
} else {
int pos = 0;
for (int i = 1; i <= n; i++)
if (a[i] != i - 1) pos = i;
a[pos] = val;
ans.push_back(pos);
}
}
cout << ans.size() << endl;
for (auto x : ans) printf("%d ", x);
printf("\n");
}
return 0;
}
Problem E. Inversion SwapSort
考虑在不破坏后续元素的相对大小关系的情况下,将最小的元素移动至 a 1 a_1 a1 ,同时消耗掉所有的包含位置 1 1 1 的逆序对 ( 1 , x i ) (1,x_i) (1,xi) 贡献的操作。
则按照
a
x
i
a_{x_i}
axi 从大到小的顺序依次操作
(
1
,
x
i
)
(1,x_i)
(1,xi) 即可。
由此,我们将问题缩小了规模,可以递归解决。
时间复杂度 O ( N 2 L o g N ) O(N^2LogN) O(N2LogN) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int n, a[MAXN];
int main() {
read(n);
vector <pair <int, int>> ans;
for (int i = 1; i <= n; i++)
read(a[i]);
for (int i = 1; i <= n; i++) {
vector <int> p;
for (int j = i + 1; j <= n; j++)
if (a[j] < a[i]) p.push_back(j);
sort(p.begin(), p.end(), [&] (int x, int y) {
if (a[x] != a[y]) return a[x] > a[y];
else return x > y;
});
for (auto x : p) ans.emplace_back(i, x);
}
cout << ans.size() << endl;
for (auto x : ans) printf("%d %d\n", x.first, x.second);
return 0;
}
Problem F. Integer Game
令
a
<
b
<
c
a<b<c
a<b<c ,考虑
1
1
1 号玩家的将军局面。
则
1
1
1 号玩家可以直接取胜当且仅当
2
2
2 号玩家上一次操作了
c
c
c ,且
b
−
a
=
c
−
b
b-a=c-b
b−a=c−b
1
1
1 号玩家可以采取如下策略保证自己获胜:
(
1
)
(1)
(1) 、令
x
=
1
0
9
+
7
x=10^9+7
x=109+7 ,保证在
2
2
2 号玩家完成操作后,被操作的数是最大的。
(
2
)
(2)
(2) 、令
a
<
b
<
c
,
x
=
2
c
−
b
−
a
a<b<c,x=2c-b-a
a<b<c,x=2c−b−a ,由于
2
2
2 号玩家无法操作
c
c
c ,此时其无论如何操作,三个数都将形成一个等差数列,或是
b
,
c
,
2
c
−
b
b,c,2c-b
b,c,2c−b ,或是
a
,
c
,
2
c
−
a
a,c,2c-a
a,c,2c−a 。
(
3
)
(3)
(3) 、令
a
<
b
<
c
,
x
=
c
−
b
a<b<c,x=c-b
a<b<c,x=c−b ,可以保证
2
2
2 号玩家无法操作。
时间复杂度 O ( 1 ) O(1) O(1) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
ll a[4]; int res;
void getres(int &res) {
cin >> res;
if (res == 0) exit(0);
}
ll gety() {
ll Max = 0, ans = 0;
for (int i = 1; i <= 3; i++)
chkmax(Max, a[i]);
for (int i = 1; i <= 3; i++)
if (Max == a[i]) ans += a[i] * 2;
else ans -= a[i];
return ans;
}
int main() {
read(a[1]), read(a[2]), read(a[3]);
puts("First");
int x = 1e9 + 7; cout << x << endl;
getres(res); a[res] += x;
ll y = gety(); cout << y << endl;
getres(res); a[res] += y;
sort(a + 1, a + 4);
assert(a[2] - a[1] == a[3] - a[2]);
cout << a[2] - a[1] << endl;
getres(res), assert(false);
return 0;
}
Problem G. Tree Modification
枚举最终形成的菊花的中心 x x x ,考虑如何以最少的操作次数完成转化。
令距离 x x x 为偶数的节点数为 A n s Ans Ans ,则不难构造出一种 A n s − 1 Ans-1 Ans−1 次操作的方案。并且,考虑图的二分图染色,可以发现,一次操作仅改变了一个点的颜色,因此 A n s − 1 Ans-1 Ans−1 也是操作次数的下界。
由此,答案即为将图二分图染色后,点数较少的颜色的点数
−
1
-1
−1 。
由于写程序的时候没有分析透彻,下文的代码使用了换根树形 DP 的方式计算了以上数值。
时间复杂度 O ( N ) O(N) O(N) 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int ans, dp[MAXN][2], sum[MAXN][2];
vector <int> a[MAXN];
void update(int pos) {
dp[pos][0] = sum[pos][1] + 1;
dp[pos][1] = sum[pos][0];
}
void getdp(int pos, int fa) {
for (auto x : a[pos])
if (x != fa) {
getdp(x, pos);
sum[pos][0] += dp[x][0];
sum[pos][1] += dp[x][1];
}
update(pos);
}
void getans(int pos, int fa) {
chkmin(ans, dp[pos][0] - 1);
for (auto x : a[pos])
if (x != fa) {
sum[pos][0] -= dp[x][0];
sum[pos][1] -= dp[x][1];
update(pos);
sum[x][0] += dp[pos][0];
sum[x][1] += dp[pos][1];
update(x);
getans(x, pos);
sum[x][0] -= dp[pos][0];
sum[x][1] -= dp[pos][1];
update(x);
sum[pos][0] += dp[x][0];
sum[pos][1] += dp[x][1];
update(pos);
}
}
int main() {
int n; read(n);
for (int i = 1; i <= n - 1; i++) {
int x, y; read(x), read(y);
a[x].push_back(y);
a[y].push_back(x);
}
ans = n + 1;
getdp(1, 0);
getans(1, 0);
cout << ans << endl;
return 0;
}
Problem H. Set Merging
首先,考虑一个操作次数 N 2 N^2 N2 的解法,我们需要对所有区间,求出对应的集合。
考虑对权值分治,令 M = ⌊ N 2 ⌋ M=\lfloor\frac{N}{2}\rfloor M=⌊2N⌋ ,首先求出数组 a i a_i ai 权值在 [ 1 , M ] [1,M] [1,M] 内的子序列所有区间对应的集合,以及数组 a i a_i ai 权值在 [ M + 1 , N ] [M+1,N] [M+1,N] 内的子序列所有区间对应的集合。此时,原数组的任意一个区间对应的集合都可以通过不少于一次操作获得。
考虑分析其操作次数
T
(
N
)
T(N)
T(N) ,有
T
(
N
)
=
(
N
+
1
2
)
+
T
(
⌊
N
2
⌋
)
+
T
(
⌈
N
2
⌉
)
≈
N
2
T(N)=\binom{N+1}{2}+T(\lfloor\frac{N}{2}\rfloor)+T(\lceil\frac{N}{2}\rceil)\approx N^2
T(N)=(2N+1)+T(⌊2N⌋)+T(⌈2N⌉)≈N2
对于原问题,考虑对权值进行分块处理,令块长为
B
B
B ,则总操作次数约为
N
B
×
B
2
+
Q
×
N
B
\frac{N}{B}\times B^2+Q\times \frac{N}{B}
BN×B2+Q×BN
那么,令
B
=
Q
B=\sqrt{Q}
B=Q 即可。
时间复杂度
O
(
N
2
+
N
Q
)
O(N^2+N\sqrt{Q})
O(N2+NQ) ,操作次数约为
2
N
Q
2N\sqrt{Q}
2NQ 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5005;
const int MAXQ = 2e5 + 5;
const int Limit = 2.2e6;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
struct info {
int l[MAXN], r[MAXN];
vector <vector <int>> home;
void init(int x) {
home.resize(x + 1);
for (int i = 0; i <= x; i++)
home[i].resize(x + 1);
}
int query(int ql, int qr) {
if (l[ql] > r[qr]) return 0;
else return home[l[ql]][r[qr]];
}
} res[MAXN];
int n, q, size, a[MAXN], b[MAXN], k[MAXQ];
int tot, block, ql[MAXN], qr[MAXN], belong[MAXN];
vector <pair <int, int>> ans;
int merge(int x, int y) {
if (x == 0 || y == 0) return x + y;
ans.emplace_back(x, y);
assert(++size <= Limit);
return size;
}
info getres(int l, int r) {
if (l == r) {
info ans;
for (int i = 1; i <= n; i++) {
ans.l[i] = 1 + (i > b[l]);
ans.r[i] = (i >= b[r]);
}
ans.init(1);
ans.home[1][1] = b[l];
return ans;
}
int mid = (l + r) / 2;
info ans, ansl, ansr;
ansl = getres(l, mid);
ansr = getres(mid + 1, r);
for (int i = 1, j = 1, k = 0; i <= n; i++) {
k += (l <= a[i] && a[i] <= r);
ans.l[i] = j, ans.r[i] = k;
j += (l <= a[i] && a[i] <= r);
}
ans.init(r - l + 1);
for (int i = 1; i <= n; i++)
if (l <= a[i] && a[i] <= r) {
for (int j = i; j <= n; j++)
if (l <= a[j] && a[j] <= r) {
ans.home[ans.l[i]][ans.r[j]] = merge(ansl.query(i, j), ansr.query(i, j));
}
}
return ans;
}
int main() {
read(n), read(q), size = n;
for (int i = 1; i <= n; i++)
read(a[i]), b[a[i]] = i;
block = 1 << 8;
for (int i = 1; i <= n; i++) {
if (i % block == 1 % block) ql[++tot] = i;
qr[tot] = i, belong[i] = tot;
}
for (int i = 1; i <= tot; i++)
res[i] = getres(ql[i], qr[i]);
for (int i = 1; i <= q; i++) {
int ql, qr, cur = 0; read(ql), read(qr);
for (int j = 1; j <= tot; j++)
cur = merge(cur, res[j].query(ql, qr));
k[i] = cur;
}
printf("%d\n", size);
for (auto x : ans)
printf("%d %d\n", x.first, x.second);
for (int i = 1; i <= q; i++)
printf("%d ", k[i]);
printf("\n");
return 0;
}