A.01 Matrix Again(思维)
题意:
有一个 N × N N \times N N×N 网格。让 ( i , j ) (i, j) (i,j) 表示从上往下第 i i i 行,从左往上第 j j j 列的单元格。
您需要在每个单元格中填入 0 0 0 或 1 1 1 。请构建一个满足以下所有条件的填充网格的方法:
- 单元格 ( A 1 , B 1 ) , ( A 2 , B 2 ) … ( A M , B M ) (A_1,B_1),(A_2,B_2)\dots (A_M,B_M) (A1,B1),(A2,B2)…(AM,BM) 中包含 1 1 1 。
- 第 i i i 行中的整数总和为 M M M 。 ( 1 ≤ i ≤ N ) (1 \le i \le N) (1≤i≤N)
- 第 i i i 列中的整数总和为 M M M 。 ( 1 ≤ i ≤ N ) (1 \le i \le N) (1≤i≤N)
可以证明,在这个问题的约束条件下,至少有一种填充网格的方法可以满足条件。
分析:
设
S
k
S_k
Sk 是单元格
(
i
,
j
)
(i,j)
(i,j) 的集合,满足
i
+
j
=
k
m
o
d
N
i + j = k \bmod N
i+j=kmodN 。现在,如果我们从
S
k
S_k
Sk 中选择
M
M
M 个不同的单元格,并在这些单元格中写入
1
1
1 ,则行和与列和将为
M
M
M 。
那么接下来就是为每个
i
i
i 加入
S
A
i
+
B
i
m
o
d
N
S_{A_i + B_i \bmod N}
SAi+BimodN ,需要注意如果有重复的
A
i
+
B
i
m
o
d
N
A_i + B_i \bmod N
Ai+BimodN 中,则需要相应添加
S
k
S_k
Sk 。
代码:
#include <bits/stdc++.h>
using namespace std;
int n, m;
int main() {
int T;
T = 1;
while (T--) {
cin >> n >> m;
vector<bool> vis(n);
for (int i = 1; i <= m; i++) {
int x, y;
cin >> x >> y;
x--, y--;
vis[(x + y) % n] = 1;
}
vector<int> ans;
for (int i = 0; i < n; i++)
if (vis[i])
ans.push_back(i);
for (int i = 0; i < n; i++)
if (!vis[i] && ans.size() < m)
ans.push_back(i);
cout << n * m << endl;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
int x = i, y = (ans[j] - i + n) % n;
cout << x + 1 << " " << y + 1 << endl;
}
}
}
return 0;
}
B.Simple Math 4(数学)
题意:
求 2 N 2^N 2N 除以 2 M − 2 K 2^M - 2^K 2M−2K 的余数的最后一位数字。
分析:
如果是
N
≥
M
N \ge M
N≥M ,我们可以变换
2
N
≡
2
N
−
2
N
−
M
(
2
M
−
2
K
)
≡
2
N
−
(
M
−
K
)
(
m
o
d
2
M
−
2
K
)
2^N \equiv 2^N - 2^{N-M}(2^M - 2^K) \equiv 2^{N-(M-K)} (\bmod\ 2^M - 2^K)
2N≡2N−2N−M(2M−2K)≡2N−(M−K)(mod 2M−2K) ,这样就可以把
N
N
N 换成
N
−
(
M
−
K
)
N-(M-K)
N−(M−K) 。重复这一操作,我们就可以将其简化为
N
<
M
N < M
N<M 的情况。
那么,如果是
N
,
K
=
M
−
1
N,K = M-1
N,K=M−1 ,我们就有
2
N
=
2
M
−
2
K
2^N = 2^M - 2^K
2N=2M−2K ,所以答案是
0
0
0 。否则,有
2
N
<
2
M
−
2
K
2^N < 2^M - 2^K
2N<2M−2K ,所以答案是
2
N
m
o
d
10
2^N \bmod 10
2Nmod10 。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 998244353;
LL binpow(LL a, LL b, LL m) {
a %= m;
LL res = 1;
while (b > 0) {
if (b & 1)
res = res * a % m;
a = a * a % m;
b >>= 1;
}
return res;
}
LL n, m;
int main() {
int T;
cin >> T;
while (T--) {
LL k;
cin >> n >> m >> k;
n -= max(0ll, n - k) / (m - k) * (m - k);
if (n == m - 1 && k == m - 1)
cout << "0" << endl;
else
cout << binpow(2, n, 10) << endl;
}
return 0;
}
C.Max Permutation(图论)
题意:
输出满足以下条件的 ( 1 , 2 , … , N ) (1,2,\dots,N) (1,2,…,N) 的排列 P = ( P 1 , P 2 , … , P N ) P=(P_1,P_2,\dots,P_N) P=(P1,P2,…,PN) 的数量,并将答案对 998244353 998244353 998244353 取模。
- max ( P A i , P B i ) = C i ( 1 ≤ i ≤ M ) \max(P_{A_i},P_{B_i}) = C_i\ (1 \le i \le M) max(PAi,PBi)=Ci (1≤i≤M) .
分析:
我们构建一个有 N N N 个顶点的图 G G G ,其中每个 i i i 顶点 A i A_i Ai 和 B i B_i Bi 之间都有一条权重为 C i C_i Ci 的边。
如果有 max ( P A i , P B i ) = C i \max(P_{A_i},P_{B_i}) = C_i max(PAi,PBi)=Ci ,那么必须有 P A i , P B i ≤ C i P_{A_i},P_{B_i} \le C_i PAi,PBi≤Ci 。据此,对于每一个 i i i 我们都可以推导出一个形式为 P i ≤ X i P_i \le X_i Pi≤Xi 的条件。(如果顶点 i i i 是 G G G 中的一个孤立点,那么 X i = ∞ X_i = \infty Xi=∞ )。
假设 P i = k P_i = k Pi=k 并且我们按照 k = N , N − 1 , . . . , 1 k = N,N-1,...,1 k=N,N−1,...,1的顺序依次处理 。在此过程中,我们进行以下情况的区分。
- 如果有两个或两个以上的 i i i 满足 C i = k C_i = k Ci=k。那么所有权重为 k k k 的边都必须有一个顶点 v v v 作为端点。这里需要注意的是,如果存在这样一个顶点 v v v ,那么它就是唯一确定的。如果 v v v 不存在或 X v < k X_v < k Xv<k 不存在,那么答案就是 0 0 0 。否则,我们设为 P v = k P_v = k Pv=k。
- 如果只有一个 i i i 满足 C i = k C_i = k Ci=k。则在该边的端点中选择一个 X j ≥ k X_j \ge k Xj≥k 并设置 P j = k P_j = k Pj=k 。如果两个端点都满足条件,那么无论我们选择哪一个,情况都是一样的,因此我们将答案乘以 2 2 2 并设置 P j = k P_j = k Pj=k 。如果两个端点都不符合条件,答案就是 0 0 0 。
- 如果没有 i i i满足 C i = k C_i = k Ci=k。我们从 X j ≥ k X_j \ge k Xj≥k 和 P j P_j Pj 尚未确定的顶点中选择一个顶点,并设置为 P j = k P_j = k Pj=k 。如果不存在这样的顶点,答案就是 0 0 0 。如果存在多个这样的顶点,那么无论我们选择哪个顶点,情况都是一样的,我们将答案乘以候选顶点的数量,并为某个顶点设置 P j = k P_j = k Pj=k 。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 3e5 + 10;
const int MOD = 998244353;
int n, m;
int tmp[MAXN];
int a[MAXN];
int u, v, w;
vector<pair<int, int> > e[MAXN];
LL ans = 1, res;
int main() {
int T;
T = 1;
while (T--) {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
tmp[i] = n;
}
for (int i = 0; i < m; i++) {
cin >> u >> v >> w;
e[w].push_back(make_pair(u, v));
tmp[u] = min(tmp[u], w);
tmp[v] = min(tmp[v], w);
}
for (int i = 1; i <= n; i++) {
a[tmp[i]]++;
}
for (int i = n; i >= 1; i--) {
res += a[i];
if (e[i].size() >= 2) {
int tmp1 = -1;
if (e[i][0].first == e[i][1].first || e[i][0].first == e[i][1].second) {
tmp1 = e[i][0].first;
} else if (e[i][0].second == e[i][1].first || e[i][0].second == e[i][1].second) {
tmp1 = e[i][0].second;
} else {
ans = 0;
}
for (int j = 2; j < e[i].size(); j++) {
if (e[i][j].first != tmp1 && e[i][j].second != tmp1) {
ans = 0;
}
}
if (tmp[tmp1] < i) {
ans = 0;
}
res--;
} else if (e[i].size() == 1) {
int num = 0;
if (tmp[e[i][0].first] >= i) {
num++;
}
if (tmp[e[i][0].second] >= i) {
num++;
}
ans = (ans * num) % MOD;
if (num) {
res--;
}
} else {
ans = (ans * res) % MOD;
if (res) {
res--;
}
}
}
cout << ans << endl;
}
return 0;
}
D.Swap Permutation (数学)
题意:
给你一个 ( 1 , 2 , … , N ) (1,2,\dots,N) (1,2,…,N) 的排列组合 P = ( P 1 , P 2 , … , P N ) P=(P_1,P_2,\dots,P_N) P=(P1,P2,…,PN) 。你将执行以下操作 M M M 次:
- 选择一对整数 ( i , j ) (i, j) (i,j) ,使得 1 ≤ i < j ≤ N 1 \le i < j \le N 1≤i<j≤N ,然后交换 P i P_i Pi 和 P j P_j Pj 。
有 ( N ( N − 1 ) 2 ) M \left(\frac{N(N-1)}{2}\right)^M (2N(N−1))M 个可能的操作序列。对于其中的每一个,考虑所有运算后的值 ∑ i = 1 N − 1 ∣ P i − P i + 1 ∣ \sum\limits_{i=1}^{N-1} |P_i - P_{i+1}| i=1∑N−1∣Pi−Pi+1∣ 。求所有这些值的和,并对 998244353 998244353 998244353取模 。
分析:
将问题转化成:把 P P P 中小于或等于 k k k 的值替换为 0 0 0 ,将大于 k k k 的值替换为 1 1 1 ,再计算 ∑ i = 1 N − 1 ∣ P i − P i + 1 ∣ \sum\limits_{i=1}^{N-1} |P_i - P_{i+1}| i=1∑N−1∣Pi−Pi+1∣ 的和,并将所有 k ( 1 ≤ k ≤ N − 1 ) k(1 \le k \le N-1) k(1≤k≤N−1) 的和相加。
接下来解决 P P P 的所有元素都是 0 0 0 或 1 1 1 的问题。考虑找出每个 i i i 在操作后 ∣ P i − P i + 1 ∣ = 1 |P_i - P_{i+1}| = 1 ∣Pi−Pi+1∣=1 的序列数量。这可以通过对三个状态进行矩阵指数运算来实现,三个状态分别代表 P i P_i Pi 和 P i + 1 P_{i+1} Pi+1 中有 j ( 0 ≤ j ≤ 2 ) j(0 \le j \le 2) j(0≤j≤2) 个零。
对所有 i , k i,k i,k 进行上述运算的时间复杂度为 O ( N 2 log M ) \mathrm{O}(N^2 \log M) O(N2logM) ,但如果使用累积和或类似方法,事先计算出每个 k k k 中 i i i 的个数,使得初始状态中 P i P_i Pi 和 P i + 1 P_{i+1} Pi+1 中不大于 k k k 的元素个数为 j j j ,就可以在 O ( log M ) \mathrm{O}(\log M) O(logM) 中找到每个 k k k 的答案。
代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
const int mod = 998244353;
struct MAT {
int z[3][3];
MAT() {
memset(z, 0, sizeof(z));
}
MAT operator*(MAT y) {
MAT x = *this, ans;
for (int i = 0; i < 3; ++i) {
for (int k = 0; k < 3; ++k) {
for (int j = 0; j < 3; ++j) {
ans.z[i][j] = (ans.z[i][j] + 1ll * x.z[i][k] * y.z[k][j]) % mod;
}
}
}
return ans;
}
MAT operator^(int y) {
MAT x = *this, ans;
for (int i = 0; i < 3; ++i) {
ans.z[i][i] = 1;
}
while (y) {
if (y & 1) {
ans = ans * x;
}
x = x * x;
y >>= 1;
}
return ans;
}
};
int p[maxn], v[maxn], c[4];
int main() {
int T;
T = 1;
while (T--) {
int n, m;
cin >> n >> m;
for (int i = 1, x; i <= n; ++i) {
cin >> x;
p[x] = i;
}
int ans = 0;
c[0] = n - 1;
for (int i = 1; i < n; ++i) {
int t = p[i];
if (t != 1) {
--c[(v[t - 1] << 1) | v[t]];
}
if (t != n) {
--c[(v[t] << 1) | v[t + 1]];
}
v[t] = 1;
if (t != 1) {
++c[(v[t - 1] << 1) | v[t]];
}
if (t != n) {
++c[(v[t] << 1) | v[t + 1]];
}
MAT tmp;
tmp.z[0][0] = (1ll * n * (n - 1) / 2 - 2 * i) % mod;
tmp.z[0][1] = 2 * i;
tmp.z[1][0] = n - i - 1;
tmp.z[1][1] = (1ll * n * (n - 1) / 2 - (n - i - 1) - (i - 1)) % mod;
tmp.z[1][2] = i - 1;
tmp.z[2][1] = (n - i) * 2;
tmp.z[2][2] = (1ll * n * (n - 1) / 2 - (n - i) * 2) % mod;
MAT tn = tmp ^ m;
ans = (ans + 1ll * tn.z[0][1] * c[0] % mod + 1ll * tn.z[1][1] * (c[1] + c[2]) % mod +
1ll * tn.z[2][1] * c[3] % mod) % mod;
}
cout << ans << endl;
}
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。