2060A - Fibonacciness

暴力枚举所有的a3a_{3}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <bits/stdc++.h>
using ll = long long;
using namespace std;

int main() {
int t;
cin >> t;

while (t--) {
int a1, a2, a4, a5;
cin >> a1 >> a2 >> a4 >> a5;

int res = 0;
for (int a3 = -100; a3 <= 100; a3++) {
int cnt = 0;
cnt += a1 + a2 == a3;
cnt += a2 + a3 == a4;
cnt += a3 + a4 == a5;
res = max(res, cnt);
}
cout << res << endl;
}

return 0;
}

2060B - Farmer John’s Card Game(思维)

主要是读题。每行内部顺序无要求;如果第ii 行的最小值小于第jj 行的最小值,那么ii 应该排在jj 前面。而且如果按行按列都升序排序之后,必须是形如

036147258\begin{array}{ccc} 0 & 3 & 6 \\ 1 & 4 & 7 \\ 2 & 5 & 8 \end{array}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <bits/stdc++.h>
using ll = long long;
using namespace std;

int main() {
int t;
cin >> t;

while (t--) {
int n, m;
cin >> n >> m;

vector a(n, vector<int>(m));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> a[i][j];
}
sort(a[i].begin(), a[i].end());
}

vector<int> p(n);
iota(p.begin(), p.end(), 0);
sort(p.begin(), p.end(), [&](int i, int j) {
return a[i] < a[j];
});

bool ok = true;
for (int i = 1; i < n; i++) {
for (int j = 0; j < m; j++) {
if (a[p[i]][j] - 1 != a[p[i - 1]][j]) {
ok = false;
}
}
}

if (!ok) {
cout << -1 << endl;
} else {
for (int i = 0; i < n; i++) {
cout << p[i] + 1 << " ";
}
cout << endl;
}
}

return 0;
}

2060C - Game of Mathletes(假博弈)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <bits/stdc++.h>
using ll = long long;
using namespace std;

int main() {
int t;
cin >> t;

while (t--) {
int n, k;
cin >> n >> k;

vector<int> cnt(n + 1);
for (int i = 0; i < n; i++) {
int x;
cin >> x;
cnt[x]++;
}

int ans = 0;
for (int i = 1; i < (k + 1) / 2; i++) {
ans += min(cnt[i], cnt[k - i]);
}
if (k % 2 == 0) {
ans += cnt[k / 2] / 2;
}
cout << ans << endl;
}

return 0;
}

2060D - Subtract Min Sort(思维)

一些分析:

  • 对于前两个数,如果a1>a2a_{1}>a_{2} 那已经无解。

  • 否则设a1a2a_{1} \leqslant a_{2}。需不需要操作前两项呢?

  • 考虑a1<a2>a3a_{1}<a_{2}>a_{3},不能先操作a2a_{2}a3a_{3},因为这样会让a3a_{3} 变成 0,意味着只能a1=a2=0a_{1}=a_{2}=0,但一般做不到。所以应当先操作a1a_{1}a2a_{2}

  • 考虑a1<a2<a3>a4a_{1}<a_{2}<a_{3}>a_{4},也不能先操作a2a_{2}a3a_{3},因为这样会让a2a_{2} 变成 0,a1>a2a_{1}>a_{2} 于是不可行。

归纳下来,最优方法就是从左往右顺着操作一遍,然后看是否升序。时间复杂度线性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <bits/stdc++.h>
using ll = long long;
using namespace std;

int main() {
int t;
cin >> t;

while (t--) {
int n;
cin >> n;

vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}

for (int i = 1; i < n; i++) {
int minn = min(a[i], a[i - 1]);
a[i] -= minn;
a[i - 1] -= minn;
}

cout << (is_sorted(a.begin(), a.end()) ? "YES" : "NO") << endl;
}

return 0;
}

2060E - Graph Composition(并查集)

如果原图有边,新图却不连通,那这条边必须删去。

现在只需要补边。用原图剩下的边建图,看有多少连通块,和新图差几个连通块就需要加几条边。

用并查集判断是否连通,时间复杂度近线性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <bits/stdc++.h>
using ll = long long;
using namespace std;

struct DSU {
vector<int> p, siz;

DSU(int n) : p(n + 1), siz(n + 1, 1) {
iota(p.begin(), p.end(), 0);
}

int find(int x) {
while (x != p[x]) x = p[x] = p[p[x]]; // 路径压缩
return x;
}

bool same(int x, int y) {
return find(x) == find(y);
}

bool uno(int x, int y) {
x = find(x), y = find(y);
if (same(x, y)) return false;
siz[x] += siz[y];
p[y] = x;
return true;
}

int size(int x) {
return siz[find(x)];
}
};

int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);

int t;
cin >> t;

while (t--) {
int n, m1, m2;
cin >> n >> m1 >> m2;

vector<pair<int, int>> E1(m1);
for (auto& [u, v] : E1) {
cin >> u >> v;
u--, v--;
}

DSU dsu1(n), dsu2(n);
while (m2--) {
int u, v;
cin >> u >> v;
u--, v--;
dsu2.uno(u, v);
}

int res = 0;
for (auto& [u, v] : E1) {
if (!dsu2.same(u, v)) {
res++;
} else {
dsu1.uno(u, v);
}
}

int siz1 = 0, siz2 = 0;
for (int i = 0; i < n; i++) {
siz1 += dsu1.find(i) == i;
siz2 += dsu2.find(i) == i;
}
res -= siz2 - siz1;
cout << res << endl;
}

return 0;
}

2060F - Multiplicative Arrays(DP 组合数学)

首先如果nn 很大,这些aia_{i} 大部分都是 1,具体来说,220>1052^{20}>10^{5},所以至多只有 20 个非 1 的数,从这里入手。

dpn,ddp_{n,d} 表示a1a2ad=na_{1}a_{2}\dots a_{d}=n 的方案数,其中ai1a_{i} \neq 1,于是可以做如下转移:

dpn,ddpx,d1,xn, 1<x<ndp_{n,d}\leftarrow dp_{x,d-1}, \quad x\mid n,\ 1<x<n

表示最后一个填它某个因数,前面用已知的转移。方案数求和。

填 1。填上 1 之后总长度恰为\ell,乘积为nn 的方案数为

f(n,)=d=1Cddpn,d,f(n,\ell)=\displaystyle \sum_{d=1}^{\ell}\operatorname{C}_{\ell}^{d}dp_{n,d},

也就是选dd 个位置填不是 1 的数。

现在需要求最长长度是LL 的,那就是

=1Lf(n,)==1Ld=1Cddpn,d.\displaystyle \sum_{\ell=1}^{L}f(n,\ell) =\sum_{\ell=1}^{L}\sum_{d=1}^{\ell}\operatorname{C}_{\ell}^{d}dp_{n,d}.

这有两个\sum,复杂度不允许,利用组合数的性质化简,

=d=1L=dLCddpn,d=d=1L(=dLCd)dpn,d=d=1LCL+1d+1dpn,d.=\sum_{d=1}^{L}\sum_{\ell=d}^{L}\operatorname{C}_{\ell}^{d}dp_{n,d} =\sum_{d=1}^{L}\Bigg(\sum_{\ell=d}^{L}\operatorname{C}_{\ell}^{d}\Bigg)dp_{n,d} =\sum_{d=1}^{L}\operatorname{C}_{L+1}^{d+1}dp_{n,d}.

这就是最终答案。

时间复杂度O(DNN+tDN)\operatorname{O}(DN\sqrt{ N }+tDN),这里D=20,N=105D=20,N=10^{5}。本地能跑出来就没问题。可以用预处理因子的方法将复杂度降低到O(DNlogN+tDN)\operatorname{O}(DN\log N+tDN)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#include <bits/stdc++.h>
using ll = long long;
using namespace std;

constexpr int mod = 998244353;
constexpr int maxN = 1e5 + 8;
constexpr int maxD = 20;
ll dp[maxD][maxN]; // dp[d][n] : x_1 * x_2 * ... * x_d == n

ll qpow(ll a, ll n) {
ll res = 1;
while (n) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}

ll inv(ll n) {
return qpow(n, mod - 2);
}


int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);

for (int n = 1; n < maxN; n++) {
dp[1][n] = 1;
}

for (int d = 2; d < maxD; d++) {
for (int n = 1; n < maxN; n++) {
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) {
dp[d][n] += dp[d - 1][i];
dp[d][n] %= mod;
if (n / i != i) {
dp[d][n] += dp[d - 1][n / i];
dp[d][n] %= mod;
}
}
}
}
}

int t;
cin >> t;

while (t--) {
int N, D;
cin >> N >> D;

cout << D << " ";

for (int n = 2; n <= N; n++) {
ll res = 0;

// 数据过大,组合数不能预处理阶乘
// 先算 C(D + 1, 2)
ll CC = (D + 1LL) * D / 2;
CC %= mod; // 记得随时取模 不然下面会爆LL

for (int d = 1; d < min(maxD, D); d++) {
res += CC * dp[d][n];
res %= mod;

// 利用 C(D + 1, d + 1) 算 C(D + 1, d + 2)
CC *= (D - d);
CC %= mod;
CC *= inv(d + 2);
CC %= mod;
}

cout << res << " ";
}
cout << endl;
}

return 0;
}

附:证明

k=nmCkn=Cm+1n+1\displaystyle \sum_{k=n}^{m}\operatorname{C}_{k}^{n}=\operatorname{C}_{m+1}^{n+1}

LHS=Cnn+Cn+1n+Cn+2n++Cmn=(Cn+1n+1+Cn+1n)+Cn+2n++Cmn=Cn+2n+1+Cn+2n++Cmn==Cmn+1+Cmn=RHS\begin{aligned} \operatorname{LHS} &=\operatorname{C}_{n}^{n}+\operatorname{C}_{n+1}^{n}+\operatorname{C}_{n+2}^{n}+\dots+\operatorname{C}_{m}^{n} \\ &=\big(\operatorname{C}_{n+1}^{n+1}+\operatorname{C}_{n+1}^{n}\big)+\operatorname{C}_{n+2}^{n}+\dots+\operatorname{C}_{m}^{n} \\ &=\operatorname{C}_{n+2}^{n+1}+\operatorname{C}_{n+2}^{n}+\dots+\operatorname{C}_{m}^{n} \\ &=\cdots \\ &=\operatorname{C}_{m}^{n+1}+\operatorname{C}_{m}^{n} \\ &=\operatorname{RHS} \end{aligned}

高中还是蛮常用的。

2060G - Bugged Sort(思维)

题意 可执行任意次操作:

  • 选定两个下标iijj,将aia_ibjb_j 交换,同时将bib_iaja_j 交换。

问能否同时将a,ba,b 按升序排序。

思路 有如下结论:

  • 操作一次,aia_{i} 对面还是bib_{i}bib_{i} 对面还是aia_{i},但是上下关系会翻转(定义翻转为 swap(ai,bi)(a_{i},b_{i}));

  • 操作一次,有且仅有两组将翻转;

  • 如果n3n \geqslant 3,则必然可以交换两列而不翻转(例如要换 1 列和 2 列,操作方案是 (1,3), (2,3), (1,2))。

所以现在化为,① 可以任意排序,② 只能翻转偶数次(记翻转次数为 cnt),问是否有解。

再转换一下,不要关注中间是如何操作的,只要 ① 最终状态有序,② 初始状态和最终状态满足 翻转偶数次,就一定有操作方法,也就一定有解。

还是按 B 的思路排序:不妨翻转为ai<bia_{i}<b_{i},按aa 升序排序,如果现在bb 不是升序则无解。现在的问题是,在有序的前提下,能否 构造 出只翻转偶数次的序列。

先看两个例子:

  • 比如序列[621543]\begin{bmatrix}{\color{green}6} & 2 & 1 \\ {\color{green}5} & 4 & 3 \end{bmatrix}。直接排序得到[125346]\begin{bmatrix}1 & 2 & {\color{green}5} \\ 3 & 4 & {\color{green}6} \end{bmatrix},总共翻转奇数次,不行。而另一个有序序列是[126345]\begin{bmatrix}1 & 2 & {\color{green}6} \\ 3 & 4 & {\color{green}5} \end{bmatrix},总共翻转偶数次,可行。这就构造出了一个合法序列[126345]\begin{bmatrix}1 & 2 & {\color{green}6} \\ 3 & 4 & {\color{green}5} \end{bmatrix}

  • 再如序列[76215843]\begin{bmatrix}{\color{green}7} & {\color{green}6} & 2 & 1 \\ {\color{green}5} & {\color{green}8} & 4 & 3 \end{bmatrix}。直接排序得到[12563478]\begin{bmatrix}1 & 2 & {\color{green}5} & {\color{green}6} \\ 3 & 4 & {\color{green}7} & {\color{green}8} \end{bmatrix},总共翻转奇数次,不行。尝试将绿色部分整体翻转,得到[12783456]\begin{bmatrix}1 & 2 & {\color{green}7} & {\color{green}8} \\ 3 & 4 & {\color{green}5} & {\color{green}6} \end{bmatrix},总共翻转奇数次,但还是不行。尝试将白色部分整体翻转,也不行。可以发现这种情况总是无解。

再次强调,不要关注中间是如何操作的,只要能构造出合法的最终状态就有解。

问题一:什么时候能整体翻转?

  • 如果ai1<bi, bi1<aia_{i-1}<b_{i},\ b_{i-1}<a_{i},也即翻转后仍然有序,就可以把下标[i,n][i,n] 这一段整体翻转(也可以把下标[1,i1][1,i-1] 这一段整体翻转)。

问题二:整体翻转能够构造出合法解吗?

  • 如果某次翻转的段的长度是偶数,那翻转并不会改变 cnt 的奇偶性,没用;否则如果是奇数,就能随意改变 cnt 的奇偶性。
  • 只要存在某个段为奇数,就能随意改变 cnt 的奇偶性,一定有解;
  • 否则,cnt 的奇偶性在排序后就确定了,没有构造空间。

总结:在可能有序的前提下,如果能随意改变 cnt 的奇偶性,或者 cnt 本就是偶数,则有解。

时间复杂度O(nlogn)\operatorname{O}(n\log n),主要是排序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <bits/stdc++.h>
using ll = long long;
using namespace std;

int main() {
int t;
cin >> t;

while (t--) {
int n;
cin >> n;

vector<array<int, 2>> a(n);
int cnt = 0;
for (int i = 0; i < n; i++) {
cin >> a[i][0];
}
for (int i = 0; i < n; i++) {
cin >> a[i][1];
if (a[i][0] > a[i][1]) {
swap(a[i][0], a[i][1]);
cnt++;
}
}
sort(a.begin(), a.end());

bool good = false;
bool ok = true;
for (int i = 1; i < n; i++) {
if (a[i - 1][1] < a[i][0]) {// 翻转后仍有序
if (i % 2 == 1) {
good = true; // 这个段长为奇数,能随意改变 cnt 的奇偶性
}
} else if (a[i - 1][1] > a[i][1]) { // 不是有序的
ok = false;
}
}

if (ok && (good || cnt % 2 == 0 || n % 2 == 1)) {
cout << "Yes" << endl;
} else {
cout << "No" << endl;
}
}

return 0;
}