最近感觉自己又行了。就很想重新开始打 Codeforces,这里罗列一下从 2020/05/16 开始提交的(不是特别水的)AC 代码。想有生之年上一次黄,先定个小目标上个 1900(最高 1854,当前 1829,换号了!新账号于 2020/11/18 达成 1900+ 成就,紫名:)也太好看了吧)菜鸡 dna049 加油 下班了!

不再打除了 codeforces, atcoder, 洛谷之外的日常比赛。有空整理一下比赛,好的题目或比赛记得 star。
优雅的代码极大的避免了低级错误,边界处理至少占三分之一的工作量!

不要急着写代码,不要急着写代码,不要急着写代码,重要的事情说三遍!!!
优先做 Codeforces, Atcoder, 其它平台的题目可以在 vjudge 中提交(洛谷和 UOJ 除外),POJ 和 HDU 不在搞了。
不要为了补题,Rating,浪费太多的时间
把一个问题写成简洁清晰的代码,才算是真的理解了问题!代码能力是快速综合规划的能力。
至少先上 2100,再去写博客,分低会影响博客的质量!

准备工作

珍惜每一场比赛,不要怕掉分,提前 6 分钟,准备以下事情(加号表示有用次数,2021 年开始计算):

  1. 如果没注册,赶紧注册。身体状态不好时,用小号打,上分不易。
  2. 闹钟(每个题不要长期思考,读完题没明显思路直接跳!只要不被卡题,我上 2100 应该不难,所以闹钟很重要) +2
  3. 纸和笔(ipad + pencil + 闹钟 亦可)+0(表示:永远有用)
  4. SageMath,用于一些数据测试
  5. 打开自己的网站用于模板 +0
  6. 打开 VScode,查看 CF 比赛号,开始 cf race CF 比赛号 +0
  7. 认真读题,特别是样例特别少的时候!
  8. 计算式要列的清晰,并且最好是好输入的(think twice, code once)。+1
  9. 不要膨胀,老老实实先做简单题,没思路就直接跳过(拒绝拖延,但是一定记得要回来把简单题补了!)
  10. 有思路赶紧写(但是思路清晰再写!无论问题难与否,大家都是一样的,打好自己!),不要觉得时间很多
  11. 开 room hack!锁题前检查边界,不要最后 RE 了(根据做题时,可能的 hack 点来找 hack,可以现在 EDU 场练习如何 hack。比赛结束看看自己 room 有那些没有过最终测试的。通用 hack 点:答案超了 long long,用 ceil 向上取整。)
  12. 前面题目被卡会特别影响做后面题的心态!
  13. 注意到题目中简单的性质结论都应该写下来,拼一下可能就知道怎么做了。注意特殊问题的特殊解法。
  14. WA 之后,很可能方法错了,或者代码有问题,没有本质修改的话,不要乱提交!
  15. 有子问题的题目,两个都会做,先交分数小的。
  16. 不要写这样的代码if(a[j++]), while(a[j++]) 这种数列下标自加,特别容易出问题还不好查,特别在判断语句中。
  17. $N=10^{18}$ 一般会有公式, $N = 2 \cdot 10^5$ 一般会用到排序、树状数组,线段树,等一系列 $O(n \log n)$ 算法,$N=1000$ 一般会用到 $dp$。模 998244353 可能会用到 NFT。

Codeforces 比赛规则:每题有基础分数,每题的分数根据时间线性递减到一半(所以有些大佬先做 CD,再做 AB),在最后一次提交前每次提交减 50 分,然后按照分数排名,有 hack,但是只有你过了初例并且 锁定题目 才能 hack。
Eductional 比赛规则:与 ICPC 一致,不过一次非 AC 提交罚时为 10 分钟(ICPC 20 分钟),难度比 Div2 稍难,需要更细心。

Codeforces Gobal Round:目前实力不支持做这个,容易掉分! 正常发挥都是能加分的!

查看 Codeforces 上的过题数查看比赛 rating 折线,开始使用 cf-tool + WSL 打比赛(真香)。

题集

1313C2:经典问题:单调栈优化

首先最优答案肯定时先递增后递减的。相当于有一个制高点,枚举制高点,自然有 $O(n^2)$ 的算法。但是利用单调栈可以优化到 $O(n)$。设 l[i], r[i] 分别表示以 i 为最高点的(前,后)缀和最大值。只讨论前缀,那么求 l[i] 自然时往前找比它小的,然后继承比的小的答案了。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

int main() {
//freopen("in", "r", stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<int> a(n);
for (auto &x : a) std::cin >> x;
auto f = [&]() -> std::vector<int> {
std::vector<int> pre(n, -1);
std::stack<int> Q;
for (int i = 0; i < n; ++i) {
while (!Q.empty() && a[Q.top()] >= a[i]) Q.pop();
if (!Q.empty()) pre[i] = Q.top();
Q.push(i);
}
return pre;
};
auto g = [&]() -> std::vector<int> {
std::vector<int> nxt(n, n);
std::stack<int> Q;
for (int i = n - 1; i >= 0; --i) {
while (!Q.empty() && a[Q.top()] >= a[i]) Q.pop();
if (!Q.empty()) nxt[i] = Q.top();
Q.push(i);
}
return nxt;
};
auto pre = f(), suf = g();
std::vector<LL> b(n), c(n);
for (int i = 0; i < n; ++i) {
b[i] = LL(i - pre[i]) * a[i] + (pre[i] == -1 ? 0 : b[pre[i]]);
}
for (int i = n - 1; i >= 0; --i) {
c[i] = LL(suf[i] - i) * a[i] + (suf[i] == n ? 0 : c[suf[i]]);
}
LL r = 0;
int id = 0;
for (int i = 0; i < n; ++i) {
if (b[i] + c[i] - a[i] > r) {
r = b[i] + c[i] - a[i];
id = i;
}
}
for (int i = id; i != -1; i = pre[i]) {
for (int j = i - 1; j != pre[i]; --j) a[j] = a[i];
}
for (int i = id; i != n; i = suf[i]) {
for (int j = i + 1; j != suf[i]; ++j) a[j] = a[i];
}
for (int i = 0; i < n; ++i) std::cout << a[i] << " \n"[i == n - 1];
return 0;
}

LOJ P1823:经典问题:问有多少对元素,它们之间没有比它们都大的元素

用单调栈存可以被当前位置的人看到的人的编号,显然是单调不增的(你回头一看,看到的人升高时单调不减的)。由于有身高相同的情况,所以需要合并相同身高。

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

izlyforever 2021/1/26 23:24:58
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

int main() {
//freopen("in", "r", stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::stack<std::pair<int, int>> S;
LL r = 0;
for (int i = 0, x; i < n; ++i) {
std::cin >> x;
int cnt = 0;
while (!S.empty() && S.top().first <= x) {
if (S.top().first == x) {
cnt = S.top().second;
}
r += S.top().second;
S.pop();
}
if (!S.empty()) ++r;
S.push({x, ++cnt});
}
std::cout << r << "\n";
return 0;
}

1009F:长链剖分 dsu on tree

C++ 图论模板 长链剖分中有讲解。这个题重链剖分也可以写,但是要写成这样的(不需要编译器优化的版本):submission 105484073,更优雅更快(由于编译器优化)的写法:submission 105483361 会 RE。

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

// 为了代码简洁,树的编号以 1 开始。
std::vector<int> dsuOnTree(std::vector<std::vector<int>> &e, int rt = 1) {
int n = e.size();
// 预处理出重儿子
std::vector<int> sz(n), son(n), dep(n);
dep[rt] = 0;
std::function<int(int, int)> pdfs = [&](int u, int fa) -> int {
sz[u] = 1;
for (auto v : e[u]) if (v != fa) {
dep[v] = dep[u] + 1;
sz[u] += pdfs(v, u);
if (sz[v] > sz[son[u]]) son[u] = v;
}
return sz[u];
};
std::vector<int> ans(n);
std::vector<std::map<int, int>> mp(n);
std::function<void(int, int)> dfs = [&](int u, int fa) -> void {
if (son[u] == 0) {
ans[u] = dep[u];
mp[u].insert({dep[u], 1});
return;
}
dfs(son[u], u);
std::swap(mp[son[u]], mp[u]);
ans[u] = ans[son[u]];
auto &mpu = mp[u];
int mx = mpu[ans[u]];
auto deal = [&](int x, int c) {
auto &it = mpu[x];
it += c;
if (it > mx || (it == mx && ans[u] >= x)) {
ans[u] = x;
mx = it;
}
};
deal(dep[u], 1);
for (auto v : e[u]) if (v != fa && v != son[u]) {
dfs(v, u);
for (auto [t, x] : mp[v]) {
deal(t, x);
}
}
};
pdfs(rt,0);
dfs(rt, 0);
for (int i = 1; i < n; ++i) ans[i] -= dep[i];
return ans;
}

int main() {
//freopen("in", "r", stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<std::vector<int>> e(n + 1);
for (int i = 1; i < n; ++i) {
int u, v;
std::cin >> u >> v;
e[u].emplace_back(v);
e[v].emplace_back(u);
}
auto r = dsuOnTree(e);
for (int i = 1; i <= n; ++i) std::cout << r[i] << "\n";
return 0;
}

``` C++
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

// 为了代码简洁,树的编号以 1 开始。
std::vector<int> dsuOnTree(std::vector<std::vector<int>> &e, int rt = 1) {
int n = e.size();
// 预处理出重儿子
std::vector<int> sz(n), son(n);
std::function<void(int, int)> pdfs = [&](int u, int fa) -> void {
for (auto v : e[u]) if (v != fa) {
pdfs(v, u);
if (sz[v] > sz[son[u]]) son[u] = v;
}
sz[u] = sz[son[u]] + 1;
};
std::vector<int> ans(n);
std::function<std::vector<int>(int, int)> dfs = [&](int u, int fa) -> std::vector<int> {
if (son[u] == 0) {
ans[u] = 0;
return {1};
}
auto a = dfs(son[u], u);
ans[u] = ans[son[u]];
for (auto v : e[u]) if (v != fa && v != son[u]) {
auto tmp = dfs(v, u);
// 这里需要对齐
for (int ai = a.size() - 1, ti = tmp.size() - 1; ti >= 0; --ti, --ai) {
a[ai] += tmp[ti];
if (a[ai] > a[ans[u]] || (a[ai] == a[ans[u]] && ai > ans[u])) {
ans[u] = ai;
}
}
}
a.emplace_back(1);
if (a[ans[u]] == 1) ans[u] = sz[u] - 1;
return a;
};
pdfs(rt, 0);
dfs(rt, 0);
for (int i = 1; i < n; ++i) ans[i] = sz[i] - 1 - ans[i];
return ans;
}

int main() {
//freopen("in", "r", stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<std::vector<int>> e(n + 1);
for (int i = 1; i < n; ++i) {
int u, v;
std::cin >> u >> v;
e[u].emplace_back(v);
e[v].emplace_back(u);
}
auto r = dsuOnTree(e);
for (int i = 1; i <= n; ++i) std::cout << r[i] << "\n";
return 0;
}

gym 102832F:dsu on tree

首先,我们可以枚举 lca(i, j),也就是说每一个节点都可以当其子树的 lca,它的两个子树中元素的 lca 必然是它。因此把这个答案算成是这个节点的答案。然后就是轻重链的问题了。注意到这里答案是异或值求和,那么我们可以诸位考虑即可。这里的写法跟 600E 的写法不一致。其实也可以写成一致的样子,600E 也可以写成我这个样子。

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
83
84
85
86
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

class Node {
public:
inline static const int N = 18;
int a[N]{};
Node() {}
Node(int x) {
++a[0];
int now = 0;
while (x) {
a[++now] = x & 1;
x >>= 1;
}
}
Node operator+=(const Node &A) {
for (int i = 0; i < N; ++i) a[i] += A.a[i];
return *this;
}
};
LL deal(const Node & A, const Node & B) {
LL r = 0;
for (int i = 1; i < Node::N; ++i) {
r += (LL(A.a[0] - A.a[i]) * B.a[i] + LL(B.a[0] - B.a[i]) * A.a[i]) << (i - 1);
}
return r;
}

// 为了代码简洁,树的编号以 1 开始
LL dsuOnTree(std::vector<std::vector<int>> &e, std::vector<int> &a, int rt = 1) {
int n = a.size();
// 预处理出重儿子
std::vector<int> sz(n), son(n);
std::function<int(int, int)> pdfs = [&](int u, int fa) -> int {
sz[u] = 1;
for (auto v : e[u]) if (v != fa) {
sz[u] += pdfs(v, u);
if (sz[v] > sz[son[u]]) son[u] = v;
}
return sz[u];
};
std::vector<LL> ans(n);
std::function<std::unordered_map<int, Node>(int, int)> dfs = [&](int u, int fa) -> std::unordered_map<int, Node> {
if (son[u] == 0) return {{a[u], Node(u)}};
auto mp = dfs(son[u], u); // 这里开 O2 被编译器优化了,不然直接爆炸
mp[a[u]] += Node(u);
LL r = 0;
for (auto v : e[u]) if (v != fa && v != son[u]) {
auto tmp = dfs(v, u);
for (auto [i, x] : tmp) {
if (auto it = mp.find(i ^ a[u]); it != mp.end()) {
r += deal(it->second, x);
}
}
for (auto [i, x] : tmp) mp[i] += x;
}
ans[u] = r;
return mp;
};
pdfs(rt, rt);
dfs(rt, rt);
LL r = 0;
for (int i = 1; i < n; ++i) r += ans[i];
return r;
}

int main() {
// freopen("C:\\Users\\dna049\\cf\\in", "r", stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<int> a(n + 1);
for (int i = 1; i <= n; ++i) std::cin >> a[i];
std::vector<std::vector<int>> e(n + 1);
for (int i = 1; i < n; ++i) {
int u, v;
std::cin >> u >> v;
e[u].emplace_back(v);
e[v].emplace_back(u);
}
std::cout << dsuOnTree(e, a) << std::endl;
return 0;
}

600E:dsu on tree

不借鉴别人,自己独创的优秀写法

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

// 为了代码简洁,树的编号以 1 开始,参考:https://www.cnblogs.com/zwfymqz/p/9683124.html
std::vector<LL> dsuOnTree(std::vector<std::vector<int>> &e, std::vector<int> &a, int rt = 1) {
int n = a.size();
std::vector<int> sz(n), son(n), cnt(n);
std::vector<LL> ans(n);
// 预处理出重儿子
std::function<int(int, int)> pdfs = [&](int u, int fa) -> int {
sz[u] = 1;
for (auto v : e[u]) if (v != fa) {
sz[u] += pdfs(v, u);
if (sz[v] > sz[son[u]]) son[u] = v;
}
return sz[u];
};
// 这个函数具体问题具体分析
int mx = 0, Son = 0;
LL sm = 0;
std::function<void(int, int)> deal = [&](int u, int fa) -> void {
++cnt[a[u]];
if (cnt[a[u]] > mx) {
mx = cnt[a[u]];
sm = a[u];
} else if (cnt[a[u]] == mx) {
sm += a[u];
}
for (auto v : e[u]) if (v != fa && v != Son) {
deal(v, u);
}
};
std::function<void(int, int)> del = [&](int u, int fa) -> void {
--cnt[a[u]];
for (auto v : e[u]) if (v != fa) del(v, u);
};
std::function<void(int, int, bool)> dfs = [&](int u, int fa, bool save) -> void {
for (auto v : e[u]) if (v != fa && v != son[u]) {
dfs(v, u, 0); // 先计算轻边贡献,但最终要消除影响,防止影响轻边
}
if (son[u]) dfs(son[u], u, 1); // 统计重儿子的贡献,但不消除影响
Son = son[u];
deal(u, fa); // 暴力处理除重儿子外的贡献
Son = 0;
ans[u] = sm;
if (!save) {
del(u, fa);
sm = 0;
mx = 0;
}
};
pdfs(rt, rt);
dfs(rt, rt, 1);
return ans;
}

int main() {
//freopen("in", "r", stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<int> a(n + 1);
for (int i = 1; i <= n; ++i) std::cin >> a[i];
std::vector<std::vector<int>> e(n + 1);
for (int i = 1; i < n; ++i) {
int u, v;
std::cin >> u >> v;
e[u].emplace_back(v);
e[v].emplace_back(u);
}
auto r = dsuOnTree(e, a);
for (int i = 1; i <= n; ++i) std::cout << r[i] << " \n"[i == n];
return 0;
}

gym 102832D:无意义的序列

首先 c = 0 时特判,$c \geq 1$ 时,观察到 $a_n = c^{bit}$, bit 为 n 的二进制中 1 的个数。然后我们只需看小于 10…0(k 个 0) 时的答案即可。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

const LL M = 1e9 + 7;
LL powMod(LL x, LL n) {
LL r = 1;
while (n) {
if (n & 1) r = r * x % M;
n >>= 1; x = x * x % M;
}
return r;
}
const int N = 3e5 + 2;
LL fac[N], ifac[N];
void init() {
fac[0] = 1;
for (int i = 1; i < N; ++i) fac[i] = fac[i - 1] * i % M;
ifac[N - 1] = powMod(fac[N - 1], M - 2);
for (int i = N - 1; i; --i) ifac[i - 1] = ifac[i] * i % M;
}
LL binom(int n, int k) {
//if (n < k || n < 0) return 0;
return fac[n] * ifac[k] % M * ifac[n - k] % M;
}

LL solve(int n, int a, int c) {
LL r = 0;
for (int i = 0; i <= n; ++i) {
r += binom(n, i) * powMod(c, i + a) % M;
}
return r % M;
}
int main() {
//freopen("in", "r", stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::string s;
int c;
std::cin >> s >> c;
if (c == 0) {
std::cout << "1\n";
return 0;
}
init();
LL r = 0;
int a = 0;
for (int i = 0; i < s.size(); ++i) if (s[i] == '1') {
r += solve(s.size() - i - 1, a++, c);
}
r += powMod(c, a);
std::cout << r % M << std::endl;
return 0;
}

AtCoder ABC189F

从 0 位置出发,走到大于等于 n 的位置结束,每次平均概率在 [1, m] 步中选择步长来走,有 k 个坑,走到坑就回到起点 0。问结束前步数的期望是多少,如果无法结束就输出 -1。

做法:首先如果有连续 m 个坑(很好判断),必然无法结束,否则可以结束,我们设 dp[i] 表示从 i 出发的答案。显然 $dp[i] = 0, i \geq n$,我们从后往前跑,显然有状态转移,如果 i 位置有坑,那么 $dp[i] = dp[0]$, 否则 $dp[i] = (dp[i + 1] + \cdots dp[i + m]) / m + 1$。这个后缀和,我们可以用个变量记录下来。因此 所有的 dp[i] 都是一个 $a + b dp[0]$ 的形式,然后到最后有 $dp[0] = a + b dp[0]$ 从而就求得了结果。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

int main() {
// freopen("C:\\Users\\dna049\\cf\\in", "r", stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m, k;
std::cin >> n >> m >> k;
std::vector<int> a(k);
for (auto &x: a) std::cin >> x;
for (int i = m - 1; i < k; ++i) if (a[i] - a[i - m + 1] == m - 1) {
std::cout << "-1\n";
return 0;
}
std::vector<double> b(n + m), c(n + m);
double sb = 0, sc = 0;
for (int i = n - 1; i >= 0; --i) {
if (a.size() && i == a.back()) {
b[i] = 0;
c[i] = 1;
a.pop_back();
} else {
b[i] = sb / m + 1;
c[i] = sc / m;
}
sb += b[i] - b[i + m];
sc += c[i] - c[i + m];
}
std::cout.precision(8);
std::cout << std::fixed << b[0] / (1 - c[0]) << std::endl;
return 0;
}

gym 102940H

长为 k 取值在 [1, n] 且满足 $a_i \mid a_{i + 1}$ 的序列有多少个。

我一开始以为跟 n 的素因子有关,后来发现没法直接推公式,然后发现是一个 dp 问题,令 dp[k][x] 表示长为 k 满足 $a_i \mid a_{i + 1}$ 且每项都是 x 的因子的序列个数。答案必然就是 $\sum_{x = 1}^n dp[k - 1][x]$。复杂度 $O(k 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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

const int M = 1e9 + 7;
int main() {
//freopen("in", "r", stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, k;
std::cin >> n >> k;
std::vector<std::vector<int>> dp(k, std::vector<int>(n + 1));
for (int i = 1; i <= n; ++i) dp[0][i] = 1;
for (int i = 1; i < k; ++i) {
for (int j = 1; j <= n; ++j) {
for (int t = j; t <= n; t += j) {
(dp[i][t] += dp[i - 1][j]) %= M;
}
}
}
int r = 0;
for (int i = 1; i <= n; ++i) (r += dp[k - 1][i]) %= M;
std::cout << r << std::endl;
return 0;
}

1474D:相连同时减一

题意:每次可以同时使得相连两个数减 1,问是否能使得数组变成全 0。这个问题相当简单,因此换成,能否最多互换相连两个的值,使得原问题成立。

做法:原问题做法就是从左到右依次跑,如果跑出负数就不行,跑到最后不是 0 也不行。然后一开始我想错了,吃了两次 WA 之后,发现互换相邻两个之后原问题成立的前提是,从左到右跑和从右到左跑都不会出现负数。因此就保存左右两边跑的结果,然后只需考虑 4 个数的时候是否对就可以了。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

int main() {
//freopen("in", "r", stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
auto check = [](std::vector<int> a) {
int now = 0;
for (auto x : a) {
now = x - now;
if (now < 0) return false;
}
return now == 0;
};
int cas = 1;
std::cin >> cas;
while (cas--) {
int n;
std::cin >> n;
std::vector<int> a(n);
for (auto &x : a) std::cin >> x;
std::vector<int> b(n + 1, -1);
b[0] = 0;
for (int i = 0; i < n && b[i] >= 0; ++i) {
b[i + 1] = a[i] - b[i];
}
if (b[n] == 0) {
std::cout << "YES\n";
continue;
}
bool flag = false;
int now = 0;
for (int i = n - 1; i > 0 && now >= 0; --i) {
if (b[i - 1] >= 0 && check(std::vector<int>({b[i - 1], a[i], a[i - 1], now}))) {
flag = true;
break;
}
now = a[i] - now;
}
std::cout << (flag ? "YES\n" : "NO\n");
}
return 0;
}

1474C:乱搞题

一开始思路不清晰就写代码,写着发现有问题,被卡了挺长时间,导致 D 题差最后 10 分钟没有 debug 出一个小错误。

做法:从最大的开始找,然后删除对应的节点即可。初始值 x 必然是 a 中最大值和另一个值的和。分析好问题后再选取合适的 STL,我用的是 map,很多人用的是 multiset

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>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

int main() {
//freopen("in", "r", stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
auto f = [](std::map<int, int> mp, int x) {
std::vector<std::pair<int, int>> r;
while (!mp.empty()) {
auto it = mp.rbegin();
int u = it->first;
if (--mp[u] == 0) mp.erase(u);
if (mp.find(x - u) == mp.end()) return std::vector<std::pair<int, int>>();
r.emplace_back(x - u, u);
if (--mp[x - u] == 0) mp.erase(x - u);
x = u;
}
return r;
};
int cas = 1;
std::cin >> cas;
while (cas--) {
int n;
std::cin >> n;
std::map<int, int> mp;
for (int i = 0, x; i < 2 * n; ++i) {
std::cin >> x;
++mp[x];
}
int x;
std::vector<std::pair<int, int>> r;
int t = mp.rbegin()->first;
for (auto it = mp.begin(); it != mp.end(); ++it) {
x = t + it->first;
r = f(mp, x);
if (r.size()) break;
}
if (r.size()) {
std::cout << "YES\n";
std::cout << x << "\n";
for (auto [x, y] : r) std::cout << x << " " << y << "\n";
} else std::cout << "NO\n";
}
return 0;
}

1473F:经典最大流(最小割)问题

题意:给定长为 n($1 \leq n \leq 3000$) 的数组 a($1 \leq a_i \leq 100$), b($-10^5 \leq b_i \leq 10^5$),求 $\displaystyle \max_{i \in S} b_i$,其中集合 $S$ 满足若 $i \in S$,则任意 $0 \leq j < i$, 若 $a_i \equiv 0 \mod a_j$,那么 $j$ 也在 $S$ 中。

做法:设 $s = n$ 为源点,$t = n + 1$ 为汇点,如果 $b_i > 0$(则称 i 为正点,否则为负点),那么我们从源点 $s$ 到 $i$ 建一个容量为 $b_i$ 的边,反之我们就从 $i$ 到 $s$ 建一个容量为 $-b_i$ 的边。如果 $j < i$ 满足 $a_i \equiv 0 \mod a_j$,那么从 $j$ 到 $i$ 建一个容量为无穷大的边(一个必要的优化,直接这样建图,边太多了,根据这个性质的传递性,我们不妨找最后一个值为 $a_j$ 的点和 $i$ 相连)。
我们不妨先把所有正点全部放在 $S$ 中,然后求最小割即可,注意到满足性质的边容量是无限大的,因此我们必然会将这样的两个节点放在一起!

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

class Dinic {
int n;
// e[i] 表示第 i 条边的终点和容量,注意存边的时候 e[i ^ 1] 是 e[i] 的反向边。
// g[u] 存的是所有以 u 为起点的边,这就很像链式前向星的做法
std::vector<std::pair<int, int>> e;
std::vector<std::vector<int>> g;
// cur[u] 表示以 u 为起点当前没被增广过的边
std::vector<int> cur, h;
// h[u] 表示 bfs 从 s 到 u 的距离,如果找到了 t,那么就说明找到了增广路。
bool bfs(int s, int t) {
h.assign(n, -1);
std::queue<int> Q;
h[s] = 0;
Q.push(s);
while (!Q.empty()) {
int u = Q.front();
Q.pop();
for (auto i : g[u]) {
auto [v, c] = e[i];
if (c > 0 && h[v] == -1) {
h[v] = h[u] + 1;
if (v == t) return true;
Q.push(v);
}
}
}
return false;
}
// f 表示从 u 点出发拥有的最大流量,输出的是 u 到 t 的最大流量
int dfs(int u, int t, int f) {
if (u == t) return f;
int r = f;
for (int &i = cur[u]; i < g[u].size(); ++i) {
int j = g[u][i];
auto [v, c] = e[j];
if (c > 0 && h[v] == h[u] + 1) {
int a = dfs(v, t, std::min(r, c));
e[j].second -= a;
e[j ^ 1].second += a;
r -= a;
if (r == 0) return f;
}
}
return f - r;
}
public:
Dinic(int _n) : n(_n), g(_n) {}
void addEdge(int u, int v, int c) {
g[u].emplace_back(e.size());
e.emplace_back(v, c);
g[v].emplace_back(e.size());
e.emplace_back(u, 0);
}
int maxFlow(int s, int t) {
int r = 0;
while (bfs(s, t)) {
cur.assign(n, 0);
r += dfs(s, t, INT_MAX);
}
return r;
}
};

int main() {
// freopen("in", "r", stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<int> a(n), b(n);
for (auto &x : a) std::cin >> x;
for (auto &x : b) std::cin >> x;
std::vector<int> last(101, -1);
Dinic g(n + 2);
int r = 0;
for (int i = 0; i < n; ++i) {
if (b[i] > 0) {
r += b[i];
g.addEdge(n, i, b[i]);
} else {
g.addEdge(i, n + 1, -b[i]);
}
for (int j = 1; j <= a[i]; ++j) {
if (a[i] % j == 0 && last[j] != -1) {
g.addEdge(i, last[j], INT_MAX);
}
}
last[a[i]] = i;
}
r -= g.maxFlow(n, n + 1);
std::cout << r << std::endl;
return 0;
}

1473D:经典前缀后缀

前缀和的历史最大值和历史最小值是特别好求的。后缀和的呢,却不那么显然。考虑后缀的时候,我们实际上要考虑后缀对最后结果的贡献。比如最大值,我们需要看当前后缀是否大于 0,如果小于等于 0 就直接抛弃重新开始,否则就继续保存。最小值同理。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

int main() {
//freopen("in", "r", stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas = 1;
std::cin >> cas;
while (cas--) {
int n, m;
std::string s;
std::cin >> n >> m >> s;
std::vector<int> c(n + 2);
std::vector<std::pair<int, int>> a(n + 2), b(n + 2);
int now = 0, mx = 0, mn = 0;
for (int i = 1; i <= n; ++i) {
if (s[i - 1] == '+') {
mx = std::max(mx, ++now);
} else {
mn = std::min(mn, --now);
}
a[i] = {mn, mx};
c[i] = now;
}
int np = 0, nq = 0;
for (int i = n; i > 0; --i) {
if (s[i - 1] == '+') {
++np;
nq = std::min(++nq, 0);
} else {
--nq;
np = std::max(--np, 0);
}
b[i] = {nq, np};
}
while (m--) {
int l, r;
std::cin >> l >> r;
std::cout << std::max(a[l - 1].second, c[l - 1] + b[r + 1].second)
- std::min(a[l - 1].first, c[l - 1] + b[r + 1].first) + 1 << std::endl;
}
}
return 0;
}

1473E:经典最短路,去掉一个最长路,加上一个最短路

一条路径的权值定义为 $\sum w_{e_i} - \max e_i + \min e_i$,显然这等价于 $\min \sum (w_{e_i}) - w_{e_j} + w_{e_k}$。因此我们可以建图:比如原始边为 (u, v, w), 一个节点到了 4u 表示是原始的长度, 4u + 1 表示减去了某个边,4u + 2 表示加上了某条边,4u + 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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

using edge = std::vector<std::vector<std::pair<int, int>>>;
std::vector<LL> Dijkstra(int s, const edge &e) {
std::priority_queue<std::pair<LL, int>> h;
std::vector<LL> dist(e.size());
std::vector<int> vis(e.size());
dist[s] = 0;
h.push({0, s});
while (!h.empty()) {
auto [d, u] = h.top();
h.pop();
if (vis[u]) continue;
vis[u] = 1;
dist[u] = -d;
for (auto [v, w] : e[u]) h.emplace(d - w, v);
}
return dist;
}

int main() {
//freopen("in", "r", stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m;
std::cin >> n >> m;
edge e(4 * n);
for (int i = 0; i < m; ++i) {
int u, v, w;
std::cin >> u >> v >> w;
--u; --v;
for (int t = 0; t < 2; ++t) {
for (int j = 0; j < 4; ++j) e[4 * u + j].emplace_back(4 * v + j, w);
e[4 * u].emplace_back(4 * v + 1, 0);
e[4 * u].emplace_back(4 * v + 2, 2 * w);
e[4 * u].emplace_back(4 * v + 3, w);
e[4 * u + 1].emplace_back(4 * v + 3, 2 * w);
e[4 * u + 2].emplace_back(4 * v + 3, 0);
std::swap(u, v);
}
}
auto dist = Dijkstra(0, e);
for (int i = 7; i < 4 * n; i += 4) std::cout << dist[i] << " ";
std::cout << "\n";
return 0;
}

也可以不建成上述图,按照原始图建图,操作的时候再也可以,本质上一致更节省空间,代码稍微复杂一点。

AtCoder arc111B:经典 2 选 1

大致 有 $n$ 个盒子,每个盒子有两个数,从中取去一个数,问最多可以取多少个不同的数。

数字为节点,盒子中的两个数连边(注意可能有重边),那个连通分支是树,那么答案就是连通分支节点数减 1,否则就是连通分支节点数。(树的情况容易证明,非树的情况总可以删边,删成只有树再多一条边的情况,然后也容易证明)

AtCoder arc111E

教程

1467C:类似于把 a, b 变成 a - b 问题(代码自解释)

题目中 3 个袋子可以换成 $m(m \geq 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
26
27
28
29
30
31
32
33
34
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

int main() {
//freopen("in", "r", stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n[3]{};
std::cin >> n[0] >> n[1] >> n[2];
LL r = 0;
std::vector<std::vector<int>> a(3);
std::vector<int> c;
for (int i = 0; i < 3; ++i) {
for (int j = 0, x; j < n[i]; ++j) {
std::cin >> x;
a[i].emplace_back(x);
r += x;
}
}
std::vector<int> b;
for (int i = 0; i < 3; ++i) {
b.emplace_back(*std::min_element(a[i].begin(), a[i].end()));
}
std::sort(b.begin(), b.end());
LL ans = r - b[0] * 2 - b[1] * 2;
for (int i = 0; i < 3; ++i) {
LL tmp = r;
for (int j = 0; j < n[i]; ++j) tmp -= a[i][j] * 2;
ans = std::max(ans, tmp);
}
std::cout << ans << std::endl;
return 0;
}

1471D:GCD 问题

题意:我们称 $a, b$ 相邻,如果 $\lcm(a, b)/gcd(a, b)$ 是平方数,这当且仅当 $ab$ 是平方数。我们定义 $f(n)$ 为 $n$ 的素因子的积,那么 $a, b$ 相邻,当且仅当 $f(a) = f(b)$。这样就好了呀。由于数据范围在 $1e6$ 之间,所以可以预处理以下就可以了。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

const int N = 1e6 + 10086;
int sp[N], p[N], f[N];
int spf(){
int cnt = 1;
p[1] = 2;
for (int i = 2; i < N; i += 2) sp[i] = 2;
for (int i = 1; i < N; i += 2) sp[i] = i;
for (int i = 3; i < N; i += 2) {
if (sp[i] == i) p[++cnt] = i;
for (int j = 1; j <= cnt && p[j] <= sp[i] && i * p[j] < N; ++j) { //防止乘法溢出
sp[i * p[j]] = p[j]; // 注意到sp只被赋值一次
}
}
f[1] = 1;
for (int i = 2; i < N; ++i) {
int pi = i / sp[i];
if (pi % sp[i] == 0) f[i] = f[pi / sp[i]];
else f[i] = f[pi] * sp[i];
}
return cnt;
}

int main() {
//freopen("in", "r", stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas = 1;
std::cin >> cas;
spf();
while (cas--) {
int n;
std::cin >> n;
std::map<int, int> mp;
for (int i = 0, x; i < n; ++i) {
std::cin >> x;
++mp[f[x]];
}
int r0 = INT_MIN, now = mp[1];
for (auto [u, v] : mp) {
r0 = std::max(r0, v);
if (u != 1 && v % 2 == 0) now += v;
}
int r1 = std::max(r0, now);
int q;
std::cin >> q;
while (q--) {
LL w;
std::cin >> w;
std::cout << (w == 0 ? r0 : r1) << "\n";
}
}
return 0;
}

上面做法还是太慢了!注意到 $f(n)$ 表示 $n$ 的最小 “无平方因子” 的因子。因此可以用平方数预处理,可用下面代码加速

1
2
3
4
5
6
void init() {
for (int i = 1; i * i < N; ++i) {
int ii = i * i, cur = 0;
for (int j = ii; j < N; j += ii) f[j] = ++cur;
}
}

1471F: 图论染色问题

比赛时,读题没读懂要干嘛。

用 01 对连通图染色,使得每个 0 周围全是 1,每个 1 周围都存在一个 0. 按照题解的说法:随便选择一点置 0,然后把它周围全部染色为 1,然后找和 1 相邻的没被染色的其中任意一个染色为 0,然后把 0 周围全部染色为 1,一直继续下去。

但是上述做法代码可能写的比较别扭,因此我们可以用队列,先随便选择一点置 0,放进队列中。在队列中的被染色为 1 的是真 1,染色为 0 的表示它和某个染色为 1 的节点相连。出队列后染色为 1 的是真的 1。队首的被染色为 0,那就把和它相邻的没被染色的变成 1 放在队列中,队首的被染色为 1,那就把它相邻的全变成 0(不管有没有被染色过),若没被染色就丢进队列中。

注意到:被染色为 0 后,染色不会变化。出队列的 1 周围的染色全为 0,被染色为 0,必然是某个出了队列的 1 帮它染的。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

int main() {
//freopen("in", "r", stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas = 1;
std::cin >> cas;
while (cas--) {
int n, m;
std::cin >> n >> m;
std::vector<std::vector<int>> e(n);
for (int i = 0, u, v; i < m; ++i) {
std::cin >> u >> v;
--u;
--v;
e[u].emplace_back(v);
e[v].emplace_back(u);
}
std::vector<int> val(n, -1);
val[0] = 0;
std::queue<int> Q;
Q.push(0);
while (!Q.empty()) {
int u = Q.front();
Q.pop();
if (val[u] == 0) {
for (auto v : e[u]) {
if (val[v] == -1) {
val[v] = 1;
Q.push(v);
} else val[v] = 1;
}
} else {
for (auto v : e[u]) if (val[v] == -1) {
val[v] = 0;
Q.push(v);
}
}
}
if (*std::min_element(val.begin(), val.end()) == -1) {
std::cout << "NO\n";
} else {
std::cout << "YES\n";
std::vector<int> r;
for (int i = 0; i < n; ++i) {
if (val[i] == 0) r.emplace_back(i);
}
std::cout << r.size() << "\n";
for (auto i : r) std::cout << i + 1 << " ";
std::cout << "\n";
}
}
return 0;
}

1466A:线段上 $n$ 个点,两两距离差值的所有可能个数

$n^2$ 的算法是显然的。

  1. 利用 Bitset,也是 $n^2$ 的 dp,然后用 bitset 存储数据,就可以达到 $n^2/64$ 的复杂度啦。
  2. 如果距离的最大值区间为 $N$,那么 $\sum x^{a_i} \sum x^(N - a_i)$ 中非负系数个数就是答案(所以有 $O(N\log N)$ 的做法。

1466E:求和

显然就是固定 $j$,每位每位的求和即可。

1466F:$\mathbb{Z}_2$ 上 $m$ 维向量线性无关组

注意到题目中至多两个位置非零,因此就可以用 并查集(一般情况倒是不知道有啥好办法)。注意到官方题解,可以多加一个维度,使得有每次正好有两个位置非零。

1465D:观察结果题

题意:给定由 0, 1, ? 构成的字符串,将 ? 变成 0 或 1,使得 01 字符和 10 字符权值和最小。(其中,01 权值为 x, 10 权值为 y)

不妨假设 $x \leq y$, 否则将字符串反序(x, y 互换)

做法:首先不考虑 ?,此时我们可以通过当前位置为 0(看前方 1 的个数),当前位置为 1(看前置 0 的个数)得到基础权值 W,然后注意到 01 的权值低于 10 的权值,因此当 m 个 ? 替换成 p 个 1 和 m - p 个 0 时候,把 0,放在最前面会让权值最小,因此我们可以考虑前缀问号给 0,后缀问号为 1 给答案的贡献。然后再加上基础权值 W 再加上 m(m-p)x

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::string a;
LL x, y;
std::cin >> a >> x >> y;
if (x > y) {
std::swap(x, y);
std::reverse(a.begin(), a.end());
}
int n = a.size();
std::vector<int> p[2], s[2];
p[0].resize(n + 1);
p[1].resize(n + 1);
s[0].resize(n + 1);
s[1].resize(n + 1);
for (int i = 0; i < n; ++i) {
p[0][i + 1] = p[0][i];
p[1][i + 1] = p[1][i];
if (a[i] != '?') ++p[a[i] - '0'][i + 1];
}
for (int i = n - 1; i >= 0; --i) {
s[0][i] = s[0][i + 1];
s[1][i] = s[1][i + 1];
if (a[i] != '?') ++s[a[i] - '0'][i];
}
LL ord = 0;
for (int i = 0; i < n; ++i) {
if (a[i] == '0') ord += p[1][i] * y;
if (a[i] == '1') ord += p[0][i] * x;
}
std::vector<LL> pre(n + 1), suf(n + 1);
for (int i = 0; i < n; ++i) {
pre[i + 1] = pre[i];
if (a[i] == '?') pre[i + 1] += s[1][i] * x + p[1][i] * y;
}
for (int i = n - 1; i >= 0; --i) {
suf[i] = suf[i + 1];
if (a[i] == '?') suf[i] += s[0][i] * y + p[0][i] * x;
}
int cnt = std::count(a.begin(), a.end(), '?'), tnc = 0;
LL r = ord + suf[0];
for (int i = 0; i < n; ++i) if (a[i] == '?') {
++tnc;
r = std::min(r, ord + pre[i + 1] + suf[i + 1] + x * (cnt - tnc) * tnc);
}
std::cout << r << std::endl;
return 0;
}

1465E:观察题

官方题解 清晰明了

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
LL s;
std::string a;
std::cin >> n >> s >> a;
s -= 1 << (a.back() - 'a');
a.pop_back();
s += 1 << (a.back() - 'a');
a.pop_back();
s = abs(s);
LL cnt[26]{};
for (auto x : a) ++cnt[x - 'a'];
for (int i = 25; i >= 0; --i) {
if ((s >> i) >= cnt[i]) s -= cnt[i] << i;
else {
int t = cnt[i] - (s >> i);
s -= (s >> i) << i;
if (t % 2) s = (1 << i) - s;
}
}
std::cout << (s == 0 ? "Yes" : "No") << std::endl;
return 0;
}

用桶排序和取正处理,直接起飞,复杂度骤降为 $O(n)$。

1450H:圆上配对问题

官方题解 属实精彩。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;
const LL M = 998244353;

LL powMod(LL x, LL n) {
LL r = 1;
while (n) {
if (n & 1) r = r * x % M;
n >>= 1; x = x * x % M;
}
return r;
}

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m;
std::string s;
std::cin >> n >> m >> s;

std::vector<LL> fac(n + 1), ifac(n + 1);
fac[0] = 1;
for (int i = 1; i <= n; ++i) fac[i] = fac[i - 1] * i % M;
ifac[n] = powMod(fac[n], M - 2);
for (int i = n; i >= 1; --i) ifac[i - 1] = ifac[i] * i % M;
auto binom = [&](int n, int k) {
return fac[n] * ifac[n - k] % M * ifac[k] % M;
};

int B[2] = {}, res[2] = {};
for (int i = 0; i < n; ++i) {
if (s[i] == '?') ++res[i % 2];
else if (s[i] == 'b') ++B[i % 2];
}
int F = res[0] + res[1], x = B[1] + res[1] - B[0];

LL r = 0;
for (int i = (x + n) % 2; i <= F; i += 2) {
(r += abs(x - i) * binom(F, i)) %= M;
}
r = r * powMod(2LL, F * (M - 2) % (M - 1)) % M;
std::cout << r << std::endl;
return 0;
}

1453D:简单概率题

这么简答的概率题,我竟然做了一个小时,被弱智的错误理解题意搞的头皮发麻!

题意:对一个长度为 $n$ 的 0-1 序列(首位为 1),0 表示不存档 1 表示存档,如果我们在第 i 关打赢了,那么我们进入第 i + 1 关,否则我们回到最近的一次存档处。那么战斗次数的期望就确定的。现在问题是期望值 $k$,能否给一个 $0-1$ 序列。

首先如果 $n = 1$,那么此时期望为 $\displaystyle \sum_{i = 1}^n \frac{i}{2^i} = 2$. 注意到如果 a[i] = 1,一旦到达 i 位置,那么期望与前面的部分就无关了。所以其实我们只需考虑序列 10...0。记期望为 $p_n$。那么

化简可得 $p_n = 2 (2^n - 1)$. 因此只要 k 是 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
29
30
31
32
33
34
35
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas = 1;
std::cin >> cas;
while (cas--) {
LL n;
std::cin >> n;
if (n & 1) {
std::cout << -1 << std::endl;
continue;
}
n /= 2;
std::vector<int> a;
while (n) {
int x = 1;
while ((1LL << x) <= n + 1) ++x;
--x;
n -= (1LL << x) - 1;
a.emplace_back(1);
for (int i = 1; i < x; ++i) a.emplace_back(0);
}
std::cout << a.size() << std::endl;
for (auto x : a) std::cout << x << " ";
std::cout << std::endl;
}
return 0;
}

1455B:简单被卡题

题意:第 $k$ 次移动时,可以往前移动 $k$ 个位置,或往后移动一个位置,从 0 到 $n$ 最少多少步完成。

一开始在想用 bfs 或者 dfs 或者 dp 来做,没有第一时间搞贪心。写完 bfs 之后,跑不动吐了。此题最多向后走一个位置即可。找到最小的 x 满足 $1 + \cdots x \geq n$,如果 $1 + \cdots x = n + 1$,那么答案就是 $x + 1$,否则为 $x$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;
const int N = 1e6 + 2;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas = 1;
std::cin >> cas;
while (cas--) {
int x;
std::cin >> x;
int r = 1;
while (r * (r + 1) / 2 < x) ++ r;
if (r * (r + 1) / 2 == x + 1) ++r;
std::cout << r << std::endl;
}
return 0;
}

1455E:好玩的问题

给定四个不同的点,将它们分别移动,使得称为一个正方形,且边分别和轴平行。左上角,右上角,左下角,右下角四个点明确之后,那么坐标分开讨论。利用一个事实,就是线段上,距离端点长度之和为常量。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas = 1;
std::cin >> cas;
while (cas--) {
int n = 4;
std::vector<pll> a(n);
for (auto &[x, y] : a) std::cin >> x >> y;
LL ans = INT64_MAX;
std::sort(a.begin(), a.end());
do {
LL sum = 0;
// 0 3
// 1 2
sum += abs(a[0].first - a[1].first);
sum += abs(a[2].first - a[3].first);
sum += abs(a[0].second - a[3].second);
sum += abs(a[1].second - a[2].second);
LL xr = std::max(a[3].first, a[2].first) - std::min(a[1].first, a[0].first);
LL xl = std::min(a[3].first, a[2].first) - std::max(a[1].first, a[0].first);
LL yr = std::max(a[0].second, a[3].second) - std::min(a[1].second, a[2].second);
LL yl = std::min(a[0].second, a[3].second) - std::max(a[1].second, a[2].second);
ans = std::min(ans, sum + 2 * std::max(0LL, std::max(xl, yl) - std::min(xr, yr)));
} while (std::next_permutation(a.begin(), a.end()));
std::cout << ans << std::endl;
}
return 0;
}

本场 1456 Div1, 我只能做 AB 两题,但是我可以做的快,做的优雅的,可是我没有!

1456A:经典 DP

题意:给定 0-1 字符串,需要让 $p, p + k, \cdots$ 位置都变成 1,每变一个位代价是 x,或者删除最开始的字符,这样做代价是 y。

做法:我们可以把字符串反过来,然后答案就是 a[n - p],状态转移:不删字符的情况下,a[i] 为 $s[i], s[i - k], \cdots$ 中 0 的个数。$a[i] = \min_{1 \leq t \leq i}(a[i], a[i - t] + t * y)$,所以我们可以将 a[i] - iy 添加到 set 中,然后最小值加上 当前的 iy 就是真实的最小值。

很早就想到了做法,实现的时候写的太急了,分析不过细致,把自己整吐了。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas = 1;
std::cin >> cas;
while (cas--) {
int n, p, k, x, y;
std::string s;
std::cin >> n >> p >> k >> s >> x >> y;
std::reverse(s.begin(),s.end());
std::vector<int> a(n), cnt(k);
std::set<int> S;
for (int i = 0; i < n; ++i) {
if (s[i] == '0') ++cnt[i % k];
a[i] = cnt[i % k] * x;
if (!S.empty()) a[i] = std::min(a[i], *S.begin() + i * y);
S.insert(a[i] - i * y);
}
std::cout << a[n - p] << std::endl;
}
return 0;
}

1456B:XOR 问题

题意:给定一个非降的序列,可以将相邻的两个变成它们的异或值,能否在最小的步数上,将这个序列不满足非降条件。

做法:注意到,如果有三个相邻的数最高位一致,那么答案必然是 1,因此本质上我们只需考虑 n = 60 的情况,所以,随便写就能过(所以我写的特别随便,然后被人 hack 了,我真的服了自己!)

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;
int solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (auto &x : a) std::cin >> x;
for (int i = 0; i + 2 < n; ++i) {
if ((a[i] ^ a[i + 1]) > a[i + 2]) return 1;
if (a[i] > (a[i + 1] ^ a[i + 2])) return 1;
}
int ans = INT_MAX;
for (int i = 1; i < n; ++i) {
std::vector<int> b, c;
int nb = 0;
for (int j = i - 1; j >= 0; --j) {
nb ^= a[j];
b.emplace_back(nb);
}
int nc = 0;
for (int j = i; j < n; ++j) {
nc ^= a[j];
c.emplace_back(nc);
}
for (int j = 0; j < b.size(); ++j) {
for (int k = 0; k < c.size(); ++k) if (b[j] > c[k]) {
ans = std::min(ans, j + k);
}
}
}
return ans == INT_MAX ? -1 : ans;
}
int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout << solve() << std::endl;
return 0;
}

AtCoder abc184F:假 0-1 背包,Meet in Middle

题意:给定序列,求选择其中部分,使得它们和最大且不超过 t,这不就是 0-1 背包吗?但是数据范围 $0 \leq n \leq 40, 0 \leq a_i, t \leq 10^9$,此题即使是多重背包,也可以用下面各种方法来做

  1. Meet in Middle,但是实现的时候可以有以下几种实现细节:
    • 用是 set 或 unordered set 存和更新,然后用双指针,整体复杂度 $O(n 2 ^{\frac{n}{2}})$
    • 用 Vector 存,之后排序(只需排序一个),然后用 lower_bounded 查找,复杂度同理
    • 用 Vector 存,保持有序,最后用双指针,复杂度 $O(2 ^{\frac{n}{2}})$
  2. 先 dfs 找到一个较好的解,然后每次更新解,用来剪枝。
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
// 有序 vector + 双指针,复杂度 $O(2 ^{\frac{n}{2}})$
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int solve() {
int n, t;
std::cin >> n >> t;
std::vector<int> a(n);
for (auto &x : a) std::cin >> x;
std::sort(a.rbegin(), a.rend());
auto merge = [&](std::vector<int>& a, int x) {
std::vector<int> b;
for (int i = 0, j = 0; i < a.size(); ++i) {
while (j < a.size() && a[i] < a[j] + x) {
if (a[j] + x <= t) b.emplace_back(a[j] + x);
++j;
}
b.emplace_back(a[i]);
}
swap(a, b);
};
auto get = [&](int l, int r) {
std::vector<int> x(1);
for (int i = l; i < r; ++i) {
merge(x, a[i]);
}
return x;
};
auto la = get(0, n / 2), lb = get(n / 2, n);
int ib = 0, ans = 0;
for (int i = la.size() - 1; i >= 0; --i) {
while (ib < lb.size() && la[i] + lb[ib] > t) ++ib;
if (ib != lb.size()) ans = std::max(ans, la[i] + lb[ib]);
}
return ans;
}

int main() {
// freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout << solve() << std::endl;;
return 0;
}
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
// dfs 剪枝,下述算法会被制造的数据吃掉,例如 t 为奇数,所有其它数为偶数,并且 t 很大,有特别多的解。
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int solve() {
int n, t;
std::cin >> n >> t;
std::vector<int> a(n);
for (auto &x : a) std::cin >> x;
std::sort(a.rbegin(), a.rend());
std::vector<LL> b(n + 1);
for (int i = n; i > 0; --i) b[i - 1] = a[i - 1] + b[i];
int ans = 0;
std::function<void(int, int)> dfs = [&](int now, int i) {
if (i > n || ans == t || ans - now >= b[i]) return;
ans = std::max(ans, now);
if (now + a[i] <= t) dfs(now + a[i], i + 1);
dfs(now, i + 1);
};
dfs(0, 0);
return ans;
}

int main() {
// freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout << solve() << std::endl;;
return 0;
}

1451E:交互题

题意:给定 n(为 2 的幂次,且大于 2),猜测一个长为 n ,取值在 [0, n - 1] 的数列。每次可以询问,XOR i jOR i jAND i j 中的一种($i \neq j$)。询问次数不超过 n + 1。

上面 ORAnd 用一个即可,我们这里用 AndOR 也类似。

做法:所有的数和第一个数异或(自己跟自己异或为 0,省一次查询),如果有相同的结果,那么做一个 AND 就知道第一个数为多少了,否则所有值都出现了,那么我们可以找到一个 i 使得 $r[1] \wedge r[i] = 2^n - 1$,此时 $r[1] \And r[i] = 0$,我们再找一个数 j,求 r[i] & r[j] 以及 r[1] & r[j] 再利用 a + b = a ^ b + 2 (a & b) 就可以求出 r[1] 了。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 1; i < n; ++i) {
std::cout << "XOR 1 " << i + 1 << std::endl;
std::cin >> a[i];
}
int x = -1;
std::vector<int> b(n, -1);
for (int i = 0; i < n; ++i) {
if (b[a[i]] == -1) b[a[i]] = i;
else {
std::cout << "AND " << b[a[i]] + 1 << " " << i + 1 << std::endl;
int tmp;
std::cin >> tmp;
x = tmp ^ a[i];
break;
}
}
if (x == -1) {
int t1, t2;
std::cout << "AND " << b[1] + 1 << " " << b[n - 1] + 1 << std::endl;
std::cin >> t1;
t1 = t1 * 2 + n - 2;
std::cout << "AND " << 1 << " " << b[1] + 1 << std::endl;
std::cin >> t2;
t2 = t2 * 2 + 1;
x = (n - 1 + t1 + t2) / 2 - t1;
}
std::cout << "! " << x;
for (int i = 1; i < n; ++i) std::cout << " " << (x ^ a[i]);
std::cout << std::endl;
return 0;
}

其实本题另一种处理技巧,不需要 $n$ 为 2 的幂次,只需考虑异或为 1 和 2 的 i,j。下面做法基于此想法

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 1; i < n; ++i) {
std::cout << "XOR 1 " << i + 1 << std::endl;
std::cin >> a[i];
}
int x = -1;
std::vector<int> b(n, -1);
for (int i = 0; i < n; ++i) {
if (b[a[i]] == -1) b[a[i]] = i;
else {
std::cout << "AND " << b[a[i]] + 1 << " " << i + 1 << std::endl;
int tmp;
std::cin >> tmp;
x = tmp ^ a[i];
break;
}
}
if (x == -1) {
int t1, t2;
std::cout << "AND " << 1 << " " << b[1] + 1 << std::endl;
std::cin >> t1;
std::cout << "AND " << 1 << " " << b[2] + 1 << std::endl;
std::cin >> t2;
x = t1 | (t2 & 1);
}
std::cout << "! " << x;
for (int i = 1; i < n; ++i) std::cout << " " << (x ^ a[i]);
std::cout << std::endl;
return 0;
}

1451D:很好博弈问题

题意:从 (0, 0) 开始,每次可以向上或向右走 k 个单位(但是和起点距离不能超过 d),两人轮流走,谁不能走了谁输。

显然可以简化成走一个单位,距离不超过 $\frac{d}{k}$. 没过一小会,我就想到了,若 $2 x^2 \leq \frac{d^2}{k^2} < 2 (x + 1)^2$,那么,如果 $x^2 + (x + 1)^2 \leq \frac{d^2}{k^2}$,那么先手赢,否则先手输。

注意到 $x^2 + (x + 2)^2 = 2 (x + 1)^2 + 2 > d$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas = 1;
std::cin >> cas;
while (cas--) {
LL d, k;
std::cin >> d >> k;
d = d * d / k / k;
LL x = std::sqrt(d / 2 + 0.1);
std::cout << (x * x + (x + 1) * (x + 1) <= d ? "Ashish\n" : "Utkarsh\n");
}
return 0;
}

1451C

题意:给定长为 n 的由小写字母组成的字符串 a, b,可以将 a 相邻的位置互换(因此所有位置都可以互换),也可以将长为 $k$ 且每一位都相同的字母全部变成下一个字母。

做法:一开始想排序之和贪心(这份代码),后来发现 n = 3, k = 2aab 变成 zzy 就会出问题。我还以为思路没问题,又交了一遍,属实天真。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

bool solve() {
int n, k;
std::string a, b;
std::cin >> n >> k >> a >> b;
std::sort(a.begin(), a.end());
std::sort(b.begin(), b.end());
// for (int i = 0; i < n; ++i) if (a[i] > b[i]) return 0;
int sa = 0, sb = 0, t = 0;
auto deal = [&](char c) {
for (int i = 1; i <= k; ++i) a[sa - i] = c;
sa -= k;
--t;
};
while (sb < n) {
if (sa == n) deal(b[sb]);
if (a[sa] == b[sb]) {
++sa;
++sb;
} else if (a[sa] > b[sb]) {
if (t == 0) return 0;
deal(b[sb]);
} else {
if (sa + k - 1 < n && a[sa] == a[sa + k - 1]) {
++t;
sa += k;
} else return 0;
}
}
return 1;
}

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas = 1;
std::cin >> cas;
while (cas--) {
std::cout << (solve() ? "Yes\n" : "No\n");
}
return 0;
}

此题考虑每个数的个数会更简单!怪不得他们做的这么快…

1452E:特别难的复杂度降低问题,经典问题,注意转化

题意,有两个出题人各自连续讲 k 题,每个参赛者选择去听其中一个出题人讲题,参赛者感兴趣的题在一个区间 [l, r] 问每个参赛者能听的自己感兴趣的题目的总和最大值为多少,$n$:总题目数,$m$ 参赛人数。

首先这里有一个自然的 $O(n^2 m)$ 的做法,有些人利用 Codeforces 上支持的 GCC 指令过了题…。
官方题解说:对于每个参赛者的感兴趣区间 [l, r],某一个出题人讲题区间 [i, i + k - 1],当 i 在递增时,这两个区间的交会怎样变化呢?在脑子里把区间进行平移会发现,它先增,在它们的中间相交之后递减,并且还是对称的。也就是说,两个出题人它们区间的中点和参赛者近的会被参赛者选择。因此排序之后,第一个出题人拿前缀,后一个拿后缀。所以就是求前缀和和后缀和之和的最大值。总复杂度 $O(mn + m \log m)$

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m, k;
std::cin >> n >> m >> k;
std::vector<pii> a(m);
for (auto &[l, r] : a) std::cin >> l >> r;
std::sort(a.begin(), a.end(), [](const pii &A, const pii &B) {
return A.first + A.second < B.first + B.second;
});
std::vector<int> sm(m + 1);
for (int i = 0; i + k <= n; ++i) {
int cur = 0;
for (int j = m - 1; j >= 0; --j) {
cur += std::max(0, std::min(i + k, a[j].second) - std::max(i, a[j].first - 1));
sm[j] = std::max(sm[j], cur);
}
}
int ans = sm[0];
for (int i = 0; i + k <= n; ++i) {
int cur = 0;
for (int j = 0; j < m; ++j) {
cur += std::max(0, std::min(i + k, a[j].second) - std::max(i, a[j].first - 1));
ans = std::max(ans, cur + sm[j + 1]);
}
}
std::cout << ans << std::endl;
return 0;
}

有人写了不排序的 $O(n m)$ 做法,反正我时没懂。

1452F:经典观察贪心问题

题意:有 $a_i$ 个 $2^i$ 的数,每次可以把某个 $2^{i + 1}$ 分成两个 $2^i$,给定 $x,y$,问至少需要多少次操作可以有至少 $y$ 个数小于 $2^x$。

首先,如果一个数 $2 \leq 2^l \leq 2^x$,那么分一次多一个答案,这种称作小的,可以将 $2^l$ 分成 $2^{l - x}$ 个 $2^x$,需要的次数为 $2^{l - x} + 1$。首先注意到 $l > k$ 时,分小的性价比较高,因此我们递增的考虑 $2^l > 2^x$

  • 若 $2^{l - x} \leq k$,全部搞一下就可以了
  • 否则,若 $2^{l - x} > k$,这个时候我们可以看那些小的个数是否大于等于 $k$,如果是,那结束了,否则将 $2^l$ 分割成 两个 $2^{l - 1}$,再判断,若 $2^{l - 1 - x} > k$,那么最多只会用到一个 $2^{l - 1}$,否则有一个 $2^{l - 1}$ 必然全部都拿来用了。那好了再次回到了这个判断上来了。经典!

一开始以为时线段树问题,后来一看 $n < 30$,有一堆的观察之后才好下手的!每一步的观察都要准确!才能一步一步的走向正确的答案

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, q;
std::cin >> n >> q;
std::vector<LL> a(n);
for (auto &x : a) std::cin >> x;
auto getans = [&](int x, LL y) -> LL {
LL small = 0, ans = 0;
for (int i = 0; i <= x; ++i) {
y -= a[i];
small += (a[i] << i) - a[i];
}
if (y <= 0) return 0;
auto add = [&](int i, LL t) {
ans += (t << i - x) - t;
y -= t << i - x;
small += (t << i) - (t << i - x);
};
int id = x + 1;
while (id < n) {
LL t = std::min(y >> id - x, a[id]);
if (t > 0) add(id, t);
if (t < a[id]) break;
++id;
}
if (id == n) return y > small ? -1 : y + ans;
while (y > small && id > x) {
--id;
++ans;
if (y >> id - x) add(id, 1LL);
if (id == x && y > 0) add(id, 1LL);
}
return y + ans;
};
while (q--) {
int op, x;
LL y;
std::cin >> op >> x >> y;
if (op == 1) a[x] = y;
else std::cout << getans(x, y) << std::endl;
}
return 0;
}

1440C:模拟题

题意:给定 $n \times m$ 的 0-1 矩阵,每次操作改变 $2 \times 2$ 小方块中三个位置,要求在 $nm$ 步内使得所有方块为 0。首先显然每一行可以通过 $m$ 次操作置 0,每一列同理,所以最后转化成对 $2 \times 2$ 四次内变成全 0

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas = 1;
std::cin >> cas;
while (cas--) {
int n, m;
std::cin >> n >> m;
std::vector<std::string> s(n);
for (auto &x : s) std::cin >> x;
std::vector<std::pair<int, int>> r;
auto f = [](char &x) {x = (x == '0' ? '1' : '0');};
for (int i = n - 1; i > 1; --i) {
for (int j = m - 1; j >= 0; --j) if (s[i][j] == '1') {
r.emplace_back(i, j);
r.emplace_back(i - 1, j);
f(s[i - 1][j]);
if (j > 0) {
r.emplace_back(i - 1, j - 1);
f(s[i - 1][j - 1]);
} else {
r.emplace_back(i - 1, j + 1);
f(s[i - 1][j + 1]);
}

}
}
for (int j = m - 1; j > 1; --j) {
for (int i = 0; i < 2; ++i) {
if (s[i][j] == '1') {
r.emplace_back(i, j);
r.emplace_back(i, j - 1);
r.emplace_back(1 - i, j - 1);
f(s[i][j - 1]);
f(s[1 - i][j - 1]);
}
}
}
std::deque<std::pair<int, int>> t[2];
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 2; ++j) {
t[s[i][j] - '0'].emplace_back(i, j);
}
}
auto g = [&](int i) {
r.emplace_back(t[i].back());
t[1 - i].emplace_front(t[i].back());
t[i].pop_back();
};
if (t[1].size() == 4) {
for (int i = 0; i < 3; ++i) g(1);
}
if (t[1].size() == 1) {
g(1);
for (int i = 0; i < 2; ++i) g(0);
}
if (t[1].size() == 2) {
g(1);
for (int i = 0; i < 2; ++i) g(0);
}
if (t[1].size() == 3) {
for (int i = 0; i < 3; ++i) g(1);
}
std::cout << r.size() / 3 << std::endl;
for (int i = 0; i * 3 < r.size(); ++i) {
for (int j = 0; j < 3; ++j) {
std::cout << r[i * 3 + j].first + 1 << " " << r[i * 3 + j].second + 1 << " \n"[j == 2];
}
}
}
return 0;
}

1440D:图论乱搞

题意:问一个图中是否存在 k-阶完全子图,或一个度数全大于 k 的子图。

显然,我们可以把度数小于 $k - 1$ 的节点全部踢了。即从度数从小到大的遍历,小于 $k - 1$ 踢了,等于 $k - 1$, 看这 $k$ 个点能否成为完全图,能?结束,否则,继续,直到当前度数为 $k$,或者所有点都被剔除。

没能在比赛的时候做出原因:

  • 没有注意到删点后更新度数的次数只和边数有关,所以当时觉得复杂度过不了!
  • 在完全图判断时不够自信(单次复杂度 $k^2$,但是注意到边数小于 $\frac{(k - 1)k}{2}$ 时不可能为完全图)。
  • 总复杂度 $O(m \sqrt{m} \log n)$,下面代码 998ms 飘过(在死亡的边缘疯狂试探)
  • unordered_set 是基于 hash 表的,如果不需要集合按顺序输出,可以作为优先选择。
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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

void solve() {
int n, m, k;
std::cin >> n >> m >> k;
std::vector<std::unordered_set<int>> e(n);
for (int i = 0, x, y; i < m; ++i) {
std::cin >> x >> y;
--x; --y;
e[x].insert(y);
e[y].insert(x);
}
std::set<std::pair<int, int>> d;
for (int i = 0; i < n; ++i) d.insert({e[i].size(), i});
auto check = [&](int u) -> bool {
std::vector<int> tmp(e[u].begin(), e[u].end());
for (int i = 0; i < tmp.size(); ++i) {
for (int j = 0; j < i; ++j) {
if (e[tmp[i]].count(tmp[j]) == 0) return false;
}
}
return true;
};
auto del = [&](int u) {
for (auto v : e[u]) {
d.erase({e[v].size(), v});
e[v].erase(u);
d.insert({e[v].size(), v});
--m;
}
};
while (!d.empty()) {
int du = d.begin()->first;
int u = d.begin()->second;
if (du >= k) {
std::cout << 1 << " " << d.size() << "\n";
for (auto it = d.begin(); it != d.end(); ++it) {
std::cout << (it->second + 1) << " ";
}
std::cout << "\n";
return;
}
if (du == k - 1 && k - 1 <= m * 2 / k && check(u)) {
std::cout << 2 << "\n";
std::cout << u + 1 << " ";
for (auto x : e[u]) std::cout << x + 1 << " ";
std::cout << "\n";
return;
}
d.erase(d.begin());
del(u);
}
std::cout << "-1\n";
}

int main() {
// freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas = 1;
std::cin >> cas;
while (cas--) {
solve();
}
return 0;
}

1440E: 线段树

没能在比赛的时候做出原因:

  • 线段树模板不太好用
  • 没有注意到数列必然单调递减,所以处理的时候处理复杂了
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

struct SegmentTree {
int n;
std::vector<int> mn, tag;
std::vector<LL> sm;
#define lson l, m, 2 * p
#define rson m + 1, r, 2 * p + 1
void resize() {
mn.resize(4 * n);
tag.resize(4 * n);
sm.resize(4 * n);
}
SegmentTree(int _n) : n(_n) {
resize();
}
SegmentTree(const std::vector<int> &a) {
n = a.size();
resize();
std::function<void(int, int, int)> build = [&](int l, int r, int p) {
if (l == r) {
mn[p] = sm[p] = a[l - 1];
return;
}
int m = (l + r) / 2;
build(lson);
build(rson);
pull(p);
};
build(1, n, 1);
}
void pull(int p) {
mn[p] = std::min(mn[2 * p], mn[2 * p + 1]);
sm[p] = sm[2 * p] + sm[2 * p + 1];
}
void set(int l, int r, int p, int v) {
tag[p] = mn[p] = v;
sm[p] = LL(r - l + 1) * v;
}
void push(int l, int r, int p) {
if (tag[p]) {
int m = (l + r) / 2;
set(lson, tag[p]);
set(rson, tag[p]);
tag[p] = 0;
}
}
void rangeSet(int L, int R, int v, int l, int r, int p) {
if (L <= l && R >= r) {
set(l, r, p, v);
return;
}
int m = (l + r) / 2;
push(l, r, p);
if (L <= m) rangeSet(L, R, v, lson);
if (R > m) rangeSet(L, R, v, rson);
pull(p);
}
int query(int x, int& y, int l, int r, int p) {
if (mn[p] > y) return 0;
if (x <= l && sm[p] <= y) {
y -= sm[p];
return r - l + 1;
}
int m = (l + r) / 2;
push(l, r, p);
int ans = 0;
if (x <= m) ans += query(x, y, lson);
ans += query(x, y, rson);
return ans;
}
int query(int x, int y) {
return query(x, y, 1, n, 1);
}
int bounded(int v, int l, int r, int p) {
if (mn[p] >= v) return r + 1;
if (l == r) return l;
int m = (l + r) / 2;
if (mn[2 * p] >= v) return bounded(v, rson);
return bounded(v, lson);
}
void modify(int x, int y) {
int l = bounded(y, 1, n, 1);
if (l <= x) rangeSet(l, x, y, 1, n, 1);
}
};

int main() {
// freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, q;
std::cin >> n >> q;
std::vector<int> a(n);
for (auto &x: a) std::cin >> x;
SegmentTree A(a);
while (q--) {
int op, x, y;
std::cin >> op >> x >> y;
if (op == 1) A.modify(x, y);
else std::cout << A.query(x, y) << std::endl;
}
return 0;
}

1447B:简单细节题

  • 很早就知道,任意两个可以换,要考虑非正数的个数是否为奇数。
  • 然后为奇数时,绝对值总和减去 2 倍的最大的非正数(!!!这是错了)。
  • 后来想了半天终于知道时减去绝对值最小的数。
  • 然后再之前的内容上改,写出了下面屎一样的 RE 代码!还 PE 了,最后 RE,吐了!

此代码为 RE 代码!

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>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas = 1;
std::cin >> cas;
while (cas--) {
int n, m;
std::cin >> n >> m;
n *= m;
std::vector<int> a, b;
int r = 0;
for (int i = 0, x; i < n; ++i) {
std::cin >> x;
r += abs(x);
if (x <= 0) a.emplace_back(-x);
else b.emplace_back(x);
}
if (a.size() & 1) {
r -= std::min(*std::min_element(a.begin(), a.end()), *std::min_element(b.begin(), b.end())) * 2;
}
std::cout << r << std::endl;
}
return 0;
}

1447C:经典问题:假 0-1 背包,真贪心

题意:给定 $n$ 件物品 $w_i$,给出一个 $k$ 件物品和在 $[\lfloor \frac{W}{2} \rfloor, W]$ 之间的一种方案

做法:对物品从大到小排序,然后贪心即可。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas = 1;
std::cin >> cas;
while (cas--) {
int n;
LL w;
std::cin >> n >> w;
std::vector<std::pair<int, int>> a(n);
for (int i = 0; i < n; ++i) {
std::cin >> a[i].first;
a[i].second = i + 1;
}
std::sort(a.begin(), a.end(), std::greater<>());
std::vector<int> x;
bool flag = false;
LL s = 0, h = (w + 1) / 2;
for (int i = 0; i < n; ++i) {
if (s + a[i].first <= w) {
x.emplace_back(a[i].second);
s += a[i].first;
if (s >= h) {
flag = true;
break;
}
}
}
if (flag) {
std::sort(x.begin(), x.end());
std::cout << x.size() << std::endl;
for (auto i : x) std::cout << i << " ";
std::cout << std::endl;
} else std::cout << -1 << std::endl;
}
return 0;
}

1447D:DP

就是一个简单的 DP,写完我都有点虚…

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>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m;
std::string a, b;
std::cin >> n >> m >> a >> b;
std::vector<std::vector<int>> dp(n + 1, std::vector<int>(m + 1));
int r = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
dp[i][j] = std::max({0, dp[i - 1][j] - 1, dp[i][j - 1] - 1});
if (a[i - 1] == b[j - 1]) dp[i][j] = std::max(dp[i][j], dp[i - 1][j - 1] + 2);
r = std::max(r, dp[i][j]);
}
}
std::cout << r << std::endl;
return 0;
}

144E:思维题

  • 考虑最高位,如果最高位为 1,最高位为 0 的个数都大于 1,那么它们必然不连通,所以我们要将其中的一个变得不超过 1,注意不能贪心。并且注意到如果元素个数不超过 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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<int> a(n);
for (auto &x : a) std::cin >> x;
std::function<int(std::vector<int>, int)> dfs = [&](std::vector<int> a, int now) -> int {
if (now < 0 || a.size() <= 2) return 0;
std::vector<int> b, c;
for (auto x : a) {
if ((x >> now) & 1) b.emplace_back(x);
else c.emplace_back(x);
}
return std::min(dfs(b, now - 1) + std::max(0, int(c.size() - 1)), dfs(c, now - 1) + std::max(0, int(b.size() - 1)));
};
std::cout << dfs(a, 29) << std::endl;
return 0;
}

AtCoder ABC 183f:并查集 + map

题意:N 个节点,每个节点有一个值,然后 Q 次操作:1 a b 是将 a, b 所在的群合并,2 x y 求 $x$ 所在的群中,值为 $y$ 的个数。细节优化

  • 尽量小的向大的合并,合并完小的记得清空
  • std::map 优于 std::multiset
  • std::vector<std::map<int, int>> 优于 std::map<std::map<int, int>>
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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
// freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, q;
std::cin >> n >> q;
std::vector<int> c(n), p(n);
std::iota(p.begin(), p.end(), 0);
std::vector<std::map<int, int>> mp(n);
for (auto &x : c) std::cin >> x, --x;
for (int i = 0; i < n; ++i) ++mp[i][c[i]];
std::function<int(int)> find = [&](int x) -> int {
int ans = x;
while (ans != p[ans]) ans = p[ans];
while (x != ans) {
int t = p[x];
p[x] = ans;
x = t;
}
return ans;
};
auto father = [&](int i, int pi) {
p[i] = pi;
for (auto it = mp[i].begin(); it != mp[i].end(); ++it) {
mp[pi][it->first] += it->second;
}
mp[i].clear();
};
auto merge = [&](int i, int j) {
int fi = find(i), fj = find(j);
if (fi != fj) {
if (mp[fi].size() < mp[fj].size()) father(fi, fj);
else father(fj, fi);
}
};
while (q--) {
int op, a, b;
std::cin >> op >> a >> b;
--a; --b;
if (op == 1) {
merge(a, b);
} else {
int fa = find(a);
std::cout << mp[fa][b] << std::endl;
}
}
return 0;
}

AtCoder ABC 183e:经典 DP

题意:在 $n \times m$ 的格点中,有些点可以走有些不行,每次能往右,下或右下中的一个方向走任意步(但是中间不能有非法点),问有多少种从左上角到右下角的走法。

显然 DP,然后用类和优化

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

const int M = 1e9 + 7;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m;
std::cin >> n >> m;
std::vector<std::vector<int>> a(n, std::vector<int>(m)), al(n, std::vector<int>(m)), au(n, std::vector<int>(m)), ad(n, std::vector<int>(m));
a[0][0] = 1;
std::vector<std::string> s(n);
for (auto &x : s) std::cin >> x;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (s[i][j] == '.') {
if (j > 0) (a[i][j] += al[i][j - 1]) %= M;
if (i > 0) (a[i][j] += au[i - 1][j]) %= M;
if (i > 0 && j > 0) (a[i][j] += ad[i - 1][j - 1]) %= M;
al[i][j] = au[i][j] = ad[i][j] = a[i][j];
if (j > 0) (al[i][j] += al[i][j - 1]) %= M;
if (i > 0) (au[i][j] += au[i - 1][j]) %= M;
if (i > 0 && j > 0) (ad[i][j] += ad[i - 1][j - 1]) %= M;
} else {
a[i][j] = al[i][j] = au[i][j] = ad[i][j] = 0;
}
}
}
std::cout << a[n - 1][m - 1] << std::endl;
return 0;
}

1438 数学场

A 题:$a_i = 1$ 即可
B 题:若存在 $i \neq j$ 使得 $a_i = a_j$,则 YES,否则 NO(考虑二进制)
C 题:可以根据假设强制让 $a_{i + j}$ 与 $i + j$ 有相同的奇偶性,那么必然满足条件,此问题一般化 解法
D 题:注意到首先我们可以让数列成对相等并且 $x \otimes x \otimes y = y$,所以如果 $n$ 为奇数,必然就 YES,若 $n$ 为偶数,我们成对这样搞之后,会有两个元素可能不等,又注意到它们相等当且仅当所有值异或为 0,所以搞定

代码索然无味就不写了

Atcoder ABC182F:找钱问题

给定 $n$ 种纸币,$1 = a_1 < a_2 < \cdots a_n$,且 $a_i | a_{i + 1}$,要买商品 $x$,那么可以给 $y \geq x$,找零 $y - x$,要求 $y$ 和 $y - x$ 的最少纸币表达中没有公共纸币。问所有的 $y$ 有没有种。

注意到 $y$ 和 $(y_1, \cdots, y_n)$,(其中 $y_i * a_i < a_{i + 1}$,且 $y = \sum a_i y_i$ 有一个一一对应。然后我们可以考虑 $x$ 的向量表达,然后再看 $y + x$ 和 $y$ 没公共非零项的做法。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
LL x;
std::cin >> n >> x;
std::vector<LL> a(n);
for (auto &x : a) std::cin >> x;
x %= a.back();
std::map<LL, LL> mp;
mp.insert({x, 1});
for (int i = 1; i < n; ++i) {
std::map<LL, LL> mp2;
for (auto it = mp.begin(); it != mp.end(); ++it) {
mp2[it->first / a[i]] += it -> second;
if (it->first % a[i]) mp2[it->first / a[i] + 1] += it->second;
}
for (int j = i + 1; j < n; ++j) a[j] /= a[i];
std::swap(mp, mp2); // 用 swap 更快!
}
std::cout << mp[0] + mp[1] << std::endl;
return 0;
}

凸优化借助凸包解决

详细解释放在 izlyforever

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;
using pdd = std::pair<double, double>;

const double eps = 1e-12;
int dcmp(double x) {
return fabs(x) < eps ? 0 : (x > 0 ? 1 : -1);
}
bool crossLeft(const pii &op, const pii &sp, const pii &ep) {
return (sp.first - op.first) * (ep.second - op.second)
< (sp.second - op.second) * (ep.first - op.first);
}
std::vector<pdd> convexHull(std::vector<pdd> p) {
std::sort(p.begin(), p.end());
p.erase(std::unique(p.begin(), p.end()), p.end());
int n = p.size();
std::vector<pdd> q(n + 1);
int top = 0;
for (int i = 0; i < n; ++i) {
while (top > 1 && crossLeft(q[top - 1], p[i], q[top - 2])) --top;
q[top++] = p[i];
}
int len = top;
for (int i = n - 2; i >= 0; --i) {
while (top > len && crossLeft(q[top - 1], p[i], q[top - 2])) --top;
q[top++] = p[i];
}
top -= n > 1;
q.resize(top);
return q;
}
int main() {
// freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, d, c;
std::cin >> n >> d;
std::vector<pdd> p(n);
for (auto &[x, y] : p) {
std::cin >> c >> x >> y;
x = x * d / c;
y = y * d / c;
}
auto q = convexHull(p);
auto cal = [](pdd p) {
return p.first * p.second;
};
auto deal = [](pdd a, pdd b) -> double {
a.first -= b.first; a.second -= b.second;
if (a.first * a.second < 0) {
double t = -(b.first / a.first + b.second / a.second) / 2;
if (t > 0 && t < 1) return (a.first * t + b.first) * (a.second * t + b.second);
}
return 0;
};
double r = -1;
for (auto &x : q) r = std::max(r, cal(x));
for (int i = 0; i != q.size(); ++i) {
r = std::max(r, deal(q[i], q[(i + 1) % q.size()]));
}
std::cout.precision(12);
std::cout << std::fixed << r << std::endl;
return 0;
}

Atcoder arc107C:并查集

首先,如果某两列和交换,那么给它们连边,那么只要任意两列可达,那么它们的位置最后就可以交换,也就是求每个连通分支的大小,直接广搜标记也可以做,当然了用并查集会更简单。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;
const LL M = 998244353;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, k;
std::cin >> n >> k;
std::vector<LL> fac(n + 1);
fac[0] = 1;
for (int i = 1; i <= n; ++i) fac[i] = fac[i - 1] * i % M;
std::vector<std::vector<int>> a(n, std::vector<int>(n));
for (auto &x : a) for (auto &i : x) std::cin >> i;
auto f = [&]() {
std::vector<int> p(n);
std::iota(p.begin(), p.end(), 0);
auto find = [&](int x) {
int ans = x;
while (ans != p[ans]) ans = p[ans];
while (x != ans) {
int t = p[x];
p[x] = ans;
x = t;
}
return ans;
};
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
bool flag = true;
for (int t = 0; t < n; ++t) {
if (a[i][t] + a[j][t] > k) {
flag = false;
break;
}
}
if (flag) p[find(j)] = p[find(i)];
}
}
for (int i = 0; i < n; ++i) find(i);
LL r = 1;
for (int i = 0; i < n; ++i) {
int cnt = std::count(p.begin(), p.end(), i);
r = r * fac[cnt] % M;
}
return r;
};
LL r = f();
for (int i = 0; i < n; ++i) {
for (int j = 0; j < i; ++j) {
std::swap(a[i][j], a[j][i]);
}
}
r = r * f() % M;
std::cout << r << std::endl;
return 0;
}

Atcoder arc107D:经典计算,DP 优化

将 $K$ 写成 $N$ 个形如 $2^{-i}, i \geq 0$ 之和(不计顺序),问有多少中写法。我们不妨将答案记作 dp[n][k]
那么显然 dp[n][k] = dp[n][2k] + dp[n - 1][2k - 2] + \cdots dp[n - k][0](考虑取多少个 1,那么剩下的最少要以 $\frac{1}{2}$ 为最大值,那么就等价于剩下的数乘以 2),所以令 s[a] = dp[a][0] + dp[a + 1][2] + \cdots dp[n][2(n - a)],这样我们就能迅速求出 dp[n][k]

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;
const LL M = 998244353;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, k;
std::cin >> n >> k;
std::vector<std::vector<int>> dp(n + 1, std::vector<int>(n + 1));
for (int i = 0; i <= n; ++i) dp[i][i] = 1;
std::vector<int> s(n + 1);
s[0] = 1;
auto add = [](int &x, int y) {
(x += y) >= M && (x -= M);
};
for (int i = 1; i <= n; ++i) {
for (int j = i; j > 0; --j) {
dp[i][j] = s[i - j];
if (j % 2 == 0) add(s[i - (j / 2)], dp[i][j]);
}
}
std::cout << dp[n][k] << std::endl;
return 0;
}

1442D:经典分治问题

首先这是一个很实在的问题。给定 $n$ 个单调递增的序列,从中取 $k$ 个数,但是取数的时候每次只能在序列的最前面取,也就是取最小的。求最大的和。首先注意最多只有一个序列取了一部分,其它的要么没取,要么取完(反证)

如果直接暴力写,也就是枚举那个只取了一部分的,其它的就是一个 0-1 背包,所以总复杂度为 $O(n^2 k)$,这肯定是过不了的。但是可以分治,也就是说分成两半,一半是0-1背包,另一半是原问题的子问题!这不就有了吗,经典!

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
// freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, k;
std::cin >> n >> k;
std::vector<std::vector<LL>> a(n);
for (auto &p : a) {
int cnt;
std::cin >> cnt;
p.emplace_back(0);
for (int i = 0, x; i < cnt; ++i) {
std::cin >> x;
if (i < k) p.push_back(p.back() + x);
}
}
std::vector<LL> dp(k + 1, -1e18);
dp[0] = 0;
auto merge = [&](int l, int r) {
for (int i = l; i < r; ++i) {
for (int j = k; j >= a[i].size() - 1; --j) {
dp[j] = std::max(dp[j], dp[j - a[i].size() + 1] + a[i].back());
}
}
};
LL ans = 0;
std::function<void(int, int)> divide = [&](int l, int r) {
if (l + 1 >= r) {
for (int i = 0; i < a[l].size(); ++i) ans = std::max(ans, dp[k - i] + a[l][i]);
return;
}
int m = (l + r + 1) / 2;
auto tmp = dp;
merge(l, m);
divide(m, r);

dp = tmp;
merge(m, r);
divide(l, m);
};
divide(0, n);
std::cout << ans << std::endl;
return 0;
}

本来是每一层都是一个 dp,但是 Itst 做了空间优化
Jiangly 写了一个非递归的 $\sqrt{n}$ 的做法,也很犀利。
知道在某一个性质的点上取得最值,那么不一定要把这个点求出来,可以在一定范围内把值都比较一边即可,因为极值点的判断有可能相对更为复杂,这可能就是计算机的魅力吧。

1443D:经典问题

给定一个非负数列,问是否可以通过前缀减一,后缀减一的方式使得所有的数都变成 0

一开始以为只需中间的数大于两边的最小值之和就可以,后来发现不对,然后通过自己想了一个例子,然后想到了正确做法,开心
这个问题等价与给定 $a$,求非负序列 $p, q$ 满足 $p + q = a$, $p$ 单调减,$q$ 单调递增(在保证条件下,使得$p$ 尽量大)

做法:看相邻两个数,比如左边比右边大,那么必然右边至少要做后缀减一的操作它们的差值次,也就是说,后面所有的数都要减去这个差值。反之同理,所以搞两个变量,一个是左边累减去(可以用剩余多少来标记),一个是右边累减。没跑一步判断一次。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas = 1;
std::cin >> cas;
while (cas--) {
int n;
std::cin >> n;
std::vector<int> a(n);
for (auto &x : a) std::cin >> x;
bool flag = true;
int now = a[0], cur = 0;
for (int i = 1; i < n; ++i) {
if (a[i] < cur) {
flag = false;
break;
}
if (a[i] > a[i - 1]) {
cur += a[i] - a[i - 1];
} else {
now -= a[i] - a[i - 1];
if (now < 0) {
flag = false;
break;
}
}
}
std::cout << (flag ? "YES\n" : "NO\n");
}
return 0;
}

1445D:一个 trival 的脑力问题

给定长为 $2n$ 的序列 $a$,分成两个长度为 $n$ 的序列 $p, q$ 然后 $p$ 非降,$q$ 非升,定义 $f(p, q) = \sum_{i = 1}^n |p_i - q_i|$,问所有的 $f(p, q)$ 的和为多少。

不妨设 $a$ 是有序的,平均分两半,如果 $p$ 在左边取了 $k$ 个元素,那么 $q$ 必然在右边取了 $k$ 个元素,所以无论哪种情况,$f(p, q)$ 是常数。所以结论就显然了!所以可以搞个升级版!。

答案就是 $\binom{2n}{n} \sum_{i = 1}^{n} (a_{i + n} - a_i)$,代码就不贴了。推公式把我推吐了。

此题已经被我魔改了,哈哈哈

1437C:经典问题之动态规划

给定 $1 \leq a_i \leq n$,求两两不同的正整数 $b_i$,使得 $\sum_{i = 1}^n |a_i - b_i|$ 最小。
显然只要 $b$ 的值域确定了,答案就确定了,且 $1 \leq b_i < 2n$。所以就可以对 $a_i$ 排序,然后再 1 ~ 2n - 1 中选 $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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas;
std::cin >> cas;
while (cas--) {
int n;
std::cin >> n;
std::vector<int> a(n);
for (auto &x : a) std::cin >> x;
std::vector<int> dp(n + 1, 1e9);
std::sort(a.begin(), a.end());
dp[0] = 0;
for (int i = 1; i < 2 * n; ++i) {
for (int j = n; j > 0; --j) dp[j] = std::min(dp[j], dp[j - 1] + abs(a[j - 1] - i));
}
print(dp[n]);
}
return 0;
}

1437E:最长递增子序列

在固定数组的一些元素的条件下,最小改变多少数,使得数列严格单调递增(减去标号就变成 不严格 递增了)

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;
int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, k;
std::cin >> n >> k;
std::vector<int> a(n + 2);
a[0] = -1e9 - 2, a[n + 1] = 1e9 + 2;
for (int i = 1; i <= n; ++i) {
std::cin >> a[i];
a[i] -= i;
}
std::vector<int> b(k + 2);
b[0] = 0, b[k + 1] = n + 1;
for (int i = 1; i <= k; ++i) std::cin >> b[i];
for (int i = 2; i <= k; ++i) if (a[b[i]] < a[b[i - 1]]){
print(-1);
return 0;
}
int r = n - k;
for (int i = 0; i <= k; ++i) {
int now = 0;
std::vector<int> c;
for (int j = b[i] + 1; j < b[i + 1]; ++j) {
if (a[j] >= a[b[i]] && a[j] <= a[b[i + 1]]) {
auto it = std::upper_bound(c.begin(), c.end(), a[j]);
if (it == c.end()) c.push_back(a[j]);
else *it = a[j];
}
}
r -= c.size();
}
print(r);
return 0;
}

1435C:经典选择问题

题意大致可以转化成:有 $n$ 个人,每个人有 $m$ 个值可以选择,问如何选择才能使得他们的最大值减最小值最小。

做法就是把所有可能的选择进行排序(二维,一维存值,一维存人),然后看所有人至少一次选择的时候最大值和最小值的差是多少。相当于左右两个指针在跑,复杂度为 $O(nm)$

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int m = 6;
std::vector<int> a(m);
for (auto &x : a) std::cin >> x;
std::sort(a.begin(), a.end());
int n;
std::cin >> n;
std::vector<int> b(n);
for (auto &x : b) std::cin >> x;
std::vector<pii> p;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
p.push_back({b[j] - a[i], j});
}
}
std::sort(p.begin(), p.end());
std::vector<int> cnt(n);
int r = 1e9 + 2;
for (int i = 0, j = 0, x = 0; i < p.size(); ++i) {
while (x < n && j < p.size()) {
if (cnt[p[j].second] == 0) ++x;
++cnt[p[j].second];
++j;
}
if (x < n) break;
if (--cnt[p[i].second] == 0) --x;
r = std::min(r, p[j - 1].first - p[i].first);
}
print(r);
return 0;
}

一开始想枚举最小值,三分法来做(但是我知道凸性一般是不成立的)

1435D:经典进出问题

将 $1 ~ n$ 个元素进行加入和提出操作(每个元素一次,共 $2n$)次,并且每次出的是当前集合中最小的值。
给定一个进出序列,和出的时候的元素值,求进的元素值(不合理的输出:NO)。

注意到每出一个元素,那么集合中剩下的元素都要大于出的元素,但是这个信息可以由顶元素上加限制来承载。当在加入一个新元素后,这个限制又被暂时的隐藏了。所以存在当前顶元素上是特别优质的做法。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
int now = 0;
std::vector<int> r(n);
std::stack<pii> A;
for (int i = 0, x; i < 2 * n; ++i) {
char op;
std::cin >> op;
if (op == '+') A.push({now++, 0});
else {
std::cin >> x;
if (A.empty() || A.top().second > x) {
print("NO");
return 0;
} else {
r[A.top().first] = x;
A.pop();
if (!A.empty()) A.top().second = std::max(A.top().second, x);
}
}
}
print("YES");
for (auto x : r) std::cout << x << " ";
println;
return 0;
}

此题一开始题意理解有点问题,最后竟然还 PA,结果最终评测 WA 了。

AtCoder arc106d:经典求和计算

对任意 $1 \leq x \leq K$ 求 $\sum_{1 \leq i < j \leq n} (a_i + a_j)^x$

注意到

然后二项式展开即可。

若此题 $n$ 比较小,$k$ 比较大($n$ 特别小时直接 $n^2 \log k$ 就没啥意思了),注意到二项式展开之后是个卷积形式,所以用 NFT 有 $O(nk + k \log k)$ 的做法。例如 $n < 3 \cdot 10^4, k < 10^5$ (时限 5s)

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;
const LL M = 998244353;
const LL inv2 = (M + 1) / 2;
LL powMod(LL x, LL n) {
LL r(1);
while (n) {
if (n & 1) r = r * x % M;
n >>= 1;
x = x * x % M;
}
return r;
}
int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, k;
std::cin >> n >> k;
std::vector<LL> a(n);
for (auto &x : a) std::cin >> x;
std::vector<LL> fac(k + 1), ifac(k + 1);
fac[0] = ifac[0] = 1;
for (int i = 1; i <= k; ++i) fac[i] = fac[i - 1] * i % M;
ifac[k] = powMod(fac[k], M - 2);
for (int i = k; i > 0; --i) ifac[i - 1] = ifac[i] * i % M;
std::vector<LL> s(k + 1);
for (int i = 0; i < n; ++i) {
LL p = 1;
for (int j = 0; j <= k; ++j) {
s[j] += p;
if (s[j] >= M) s[j] -= M;
p = p * a[i] % M;
}
}
auto C = [&](int n, int m) {
return fac[n] * ifac[m] % M * ifac[n - m] % M;
};
// here nft can be used
for (int i = 1; i <= k; ++i) {
int r = 0;
for (int j = 0; j <= i; ++j) {
r = (r + C(i, j) * s[j] % M * s[i - j] % M) % M;
}
r = (r - powMod(2, i) * s[i]) % M;
if (r < 0) r += M;
r = r * inv2 % M;
print(r);
}
return 0;
}

1436E: MEX

求 $MEX(MEX(L, R)_{1 \leq L \leq R \leq n}$),其中 $MEX(L, R)$ 为使得 $a_L, a_{L + 1} \cdots, a_{R}$ 中没出现的最小正整数。

做法:先求出所有 MEX(i, n),这是能在 $O(n)$ 时间复杂度解决的(因为 MEX 会随着 i 递减而增大,并且值域不超过 $n$)。然后我们删除尾部的点,那么在从右往左首次出现 $Mex(i, n) > a[n]$ 的 pre[n] + 1 ~ i 这一段的值都要改成 a[n],这里 pre[x] 表示 x 位置前一个值为 a[x] 的位置。那么区间线段树就搞定了。

没在比赛时候写出,不想写了!

hdu 4747: MEX

求 $\sum_{1 \leq L \leq R \leq n} MEX(L, R)$),其中 $MEX(L, R)$ 为使得 $a_L, a_{L + 1} \cdots, a_{R}$ 中没出现的最小自然数

同理与上面做法,代码不写了,懒得写线段树。

有的 MEX 定义包含 0, 有的不包含,无所谓啦。

1433G: 将某条边的权值置零下最短路径

我一开始以为是缩点… 想太多了,做法就是先求出任意两点的距离,然后边 x -> y 置零可以看成 a -> b 多了两种选择 a -> x - > y - > ba -> y -> x -> b 就好了。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
auto cmax = [](auto &x, auto y) {
if (x < y) x = y;
};
auto cmin = [](auto &x, auto y) {
if (x > y) x = y;
};
int n, m, k;
std::cin >> n >> m >> k;
std::vector<std::vector<int>> d(n, std::vector<int>(n, 1e9));
for (int i = 0; i < n; ++i) d[i][i] = 0;
std::vector<std::tuple<int, int, int>> road(m);
for (auto &[x, y, w] : road) {
std::cin >> x >> y >> w;
--x; --y;
cmin(d[x][y], w);
d[y][x] = d[x][y];
}
std::vector<std::pair<int, int>> travel(k);
for (auto &[a, b] : travel) {
std::cin >> a >> b;
--a; --b;
}
auto floyd = [&](){
for(int k = 0; k != n; ++k)
for(int i = 0; i != n; ++i)
for(int j = 0; j != n; ++j)
cmin(d[i][j], d[i][k] + d[k][j]);
};
floyd();
LL r = 1e9;
for (auto [x, y, w] : road) {
LL now = 0;
for (auto [a, b] : travel) {
now += std::min({d[a][b], d[a][x] + d[y][b], d[a][y] + d[x][b]});
}
cmin(r, now);
}
print(r);
return 0;
}

用堆优化 Dijkstra 会更快一些,用 priority_quque 比 set 快一些。不过 set 比 priority_quque 方便很多(好遍历删除等操作)。

439D:三分法模板题

每次操作可以增加某个数或者减少某个数,问最小多少次操作可以让 a 的最小值不小于 b 的最大值

我们可以枚举 a 最终的最小值 t,那么答案就是 $\sum_{a_i < t} (t - a_i) + \sum_{b_i > t} (b_i - t)$
求导然后就知道了它是凸函数了。然后就可以三分法求了。

三分法求凸函数最值

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m;
std::cin >> n >> m;
std::vector<int> a(n), b(m);
for (auto &x : a) std::cin >> x;
for (auto &x : b) std::cin >> x;

auto f = [&](int ma) {
LL r = 0;
for (auto x : a) if (x < ma) r += ma - x;
for (auto x : b) if (x > ma) r += x - ma;
return r;
};

int l = 1, r = 1e9;
while (l + 2 < r) {
int lm = (2ll * l + r) / 3, rm = (l + 2ll * r + 2) / 3;
if (f(lm) < f(rm)) r = rm;
else l = lm;
}
while (l < r) {
if (f(l) < f(r)) --r;
else ++l;
}

print(f(l));

return 0;
}

当然了此题有更简单的做法:将 a 升序,b 降序,然后答案就是 a[i] - b[i] 为正的和。

1421A:异或,和,交的关系

$(a \oplus x) + (x \oplus b)$ 的最小值,$a \oplus b$,此时 x = a & b

1421E:观察总结题

  • 任何情况下的答案都是 每一项前添加正负号得到的。
  • 相邻两个之间添加的正负号必然有一个是相同的,即不会出现 +-+-+-+-+- 的情况
  • 负号个数 $m$,总个数 $n$,满足 $(n + m) \equiv 1 \mod 3$(可以通过归纳得到,并且 $n$ 个正负号的情况可以看作 $n - 1$ 个正负号中某一个改变符号并且出现两个,再塞进去)
  • 所有的情况如上所言(可以数学归纳证明)

我们用 DP[n][3][2][2] 保存全部状态。DP[i][j][k][p] 分别表示当前 i 个位置,(i + 负数个数 mod 3) 等于 jk 表示是否非法(是否存在连续的正或负),p 表示最后以为是否为正的最大结果。

例如 DP[i][j][1][1] 表示前 i 位(i 与 前 i 位负号个数的和模 3 位 j) 且有连续的负号或者正号,且第 i 位为负号的最大和。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

const int N = 2e5 + 2;
LL dp[N][3][2][2];

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::memset(dp, -0x3f, sizeof(dp));
auto upmax = [](LL &a, LL b) {
if (a < b) a = b;
};
int n;
std::cin >> n;
std::vector<int> a(n);
for (auto &x : a) std::cin >> x;
if (n == 1) {
print(a[0]);
return 0;
}
dp[0][1][0][0] = a[0];
dp[0][2][0][1] = -a[0];
for (int i = 1; i < n; ++i) {
for (int j = 0; j < 3; ++j) {
for (int k = 0; k < 2; ++k) {
for (int p = 0; p < 2; ++p) {
for (int b = 0; b < 2; ++b) {
int digit = (j + 1 + b) % 3;
upmax(dp[i][digit][k | (p == b)][b], dp[i - 1][j][k][p] + (b == 0 ? a[i]: -a[i]));
}
}
}
}
}
print(std::max(dp[n - 1][1][1][0], dp[n - 1][1][1][1]));
return 0;
}

此题多维数组写 Vector 不方便,但是又不能开局部数组(因为太大了),所以只能全局变量啦。

1428D:模拟题

由简单到复杂一步步的来,很不错的题。

1428E:经典问题,正整数划分最值问题(可看作优先队列模板题)

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;
using pii = std::pair<int, int>;
using pll = std::pair<LL, LL>;

LL val(const int &x, const int &n) {
int xn = x / n, rn = x - xn * n;
return 1ll * xn * xn * (n - rn) + 1ll * (xn + 1) * (xn + 1) * rn;
}
LL cmpVal(const pii &A) {
return val(A.first, A.second) - val(A.first, A.second + 1);
}
class cmp {
public:
bool operator() (const pii &lhs, const pii &rhs) const {
return cmpVal(lhs) < cmpVal(rhs);
}
};

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, k;
std::cin >> n >> k;
std::priority_queue<pii, std::vector<pii>, cmp> Q;
for (int i = 0, x; i < n; ++i) {
std::cin >> x;
Q.push({x, 1});
}
while (n < k) {
++n;
auto [x, cnt] = Q.top();
Q.pop();
Q.push({x, cnt + 1});
}
LL r = 0;
while (!Q.empty()) {
auto [x, cnt] = Q.top();
Q.pop();
r += val(x, cnt);
}
print(r);
return 0;
}

若此题 $k$ 特别大,可二分 cmpVal 的题来解决。

1428F: 经典 DP

题意:给定 0-1 序列 S,设 $f(l, r)$ 表示 $S_l S_{l + 1} \cdots, S_{r}$ 中最长连续 1 的个数,求 $\sum_{l = 1}^n \sum_{r = l}^n f(l, r)$

官方题解 实在是太精彩了!这种直方图的做法真的很 Nice!

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::string s;
std::cin >> n >> s;
LL ans = 0, cur = 0;
std::vector<int> left(n + 1, -1);
for (int i = 0; i < n; ++i) {
if (s[i] == '0') ans += cur;
else {
int l = i, r = i;
while (r < n && s[r] == '1') ++r;
int len = 0;
for (i = l; i < r; ++i) {
++len;
cur += i - left[len];
ans += cur;
left[len] = r - len;
}
--i;
}
}
print(ans);
return 0;
}

1430F:反向 DP

我想到反向 DP (DP[i] 表示这次本次要预留的子弹数为多少),但是还是不明确怎么写。我们可以看每一波的时候至少需要多少子弹预留。然后合理性检测,最后再从头到尾走一波就知道用多少子弹。把检测和计算分两次搞。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, k;
std::cin >> n >> k;
std::vector<int> a(n), l(n), r(n), dp(n);
for (int i = 0; i < n; ++i) std::cin >> l[i] >> r[i] >> a[i];
for (int i = n - 1; i >= 0; --i) {
int need = a[i];
if (i < n - 1 && r[i] == l[i + 1]) need += dp[i + 1];
if (LL(r[i] - l[i] + 1) * k < need) {
print(-1);
return 0;
}
dp[i] = std::max(0LL, need - LL(r[i] - l[i]) * k);
}
int cur = k;
LL ans = 0;
for (int i = 0; i < n; ++i) {
ans += a[i];
if (cur < dp[i]) {
ans += cur;
cur = k;
}
cur = ((cur - a[i]) % k + k) % k;
}
print(ans);
return 0;
}

1430E:可以转化成求逆序数

这种只能交换相邻位置的问题,一般都能转换成逆序数。我们先根据最后的状态来定义序关系,然后对应赋值,再求逆序数就好了!

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;

struct TreeArray {
std::vector<LL> s;
TreeArray() {}
TreeArray(int n) { init(n); }
void init(int n) {
s.resize(n + 1);
std::fill(s.begin(), s.end(), 0);
}
int lowbit(int n) {
return n & (-n);
}
void add(int id, int p) {
while (id < s.size()) {
s[id] += p;
id += lowbit(id);
}
}
LL sum(int id) {
LL r = 0;
while (id) {
r += s[id];
id -= lowbit(id);
}
return r;
}
// find minimal index s.t. sum(id) >= x, sum must be increased
int search(LL val) {
LL sum = 0;
int id = 0;
for (int i = std::__lg(s.size()); ~i; --i) {
if (id + (1 << i) < s.size() && sum + s[id + (1 << i)] < val) {
id += (1 << i);
sum += s[id];
}
}
return ++id;
}
};

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::string s;
std::cin >> n >> s;
std::vector<int> tmp[26];
int now = 0;
for (int i = s.size() - 1; i >= 0; --i) tmp[s[i] - 'a'].emplace_back(++now);
std::vector<int> a(n + 1), p(n + 1);
for (int i = n; i > 0; --i) {
a[i] = tmp[s[i - 1] - 'a'].back();
tmp[s[i - 1] - 'a'].pop_back();
}
for (int i = 1; i <= n; ++i) p[a[i]] = i;
TreeArray A(n);
LL r = 0;
for (int i = n; i > 0; --i) {
r += A.sum(p[i]);
A.add(p[i], 1);
}
print(r);
return 0;
}

清华集训 2016 组合数问题

给定 $1 \leq n, m \leq 10^{18}$ 和素数 $p$,求所有 $0 \leq i \leq n, 0 \leq j \leq \min(i,m)$ 中有多少对 $(i, j)$ 满足 ${i \choose j}$ 是 $p$ 的倍数。

${i \choose j}$ 是 $p$ 的倍数当且仅当 $i, j$ 的 $p$ 进制中至少有一位 $x$ 满足 $i_x < j_x$(利用 Lucas 定理显然)。

所以最终答案就是

其中 $k = \max(0, n - m)$,$f(n, m)$ 表示 $i$ 的 每一位都满足 $i_x \geq j_x$ 的方案数,注意到此时必有 $i > j$,所以这里的 $i, j$ 限制分别是 $0 \leq i \leq n, 0 \leq j \leq m$. 并且可以让 $m = \min(m, n)$,我们可以从最低位开始 DP 就好了。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;
const LL M = 1e9 + 7;
LL inv2 = (M + 1) / 2;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas, p;
std::cin >> cas >> p;
while (cas--) {
LL n, m;
std::cin >> n >> m;
m = std::min(m, n);
LL r = (2 * n + 2 - m) % M * ((m + 1) % M) % M * inv2 % M;
std::vector<int> an, bm;
auto digit = [&]() {
int r = 0;
while (n) {
an.emplace_back(n % p);
bm.emplace_back(m % p);
n /= p;
m /= p;
++r;
}
return r;
};
int d = digit();
// dp[i][sa][sb]; sa, sb 分别表示 $a, b$ 第 $i$ 为是否有限制
LL dp[d + 1][2][2];
for (int i = 0; i < 4; ++i) dp[0][i / 2][i % 2] = 1;
for (int i = 0; i < d; ++i) {
dp[i + 1][0][0] = (p + 1) * p / 2 * dp[i][0][0] % M;
dp[i + 1][0][1] = ((2 * p - bm[i] + 1) * bm[i] / 2 * dp[i][0][0] + (p - bm[i]) * dp[i][0][1]) % M;
dp[i + 1][1][0] = ((an[i] + 1) * an[i] / 2 * dp[i][0][0] + (an[i] + 1) * dp[i][1][0]) % M;
dp[i + 1][1][1] = 0;
if (an[i] >= bm[i]) dp[i + 1][1][1] += (an[i] - bm[i]) * dp[i][0][1] + dp[i][1][1];
else dp[i + 1][1][1] += dp[i][1][0];
bm[i] = std::min(bm[i], an[i]);
(dp[i + 1][1][1] += (2 * an[i] + 1 - bm[i]) * bm[i] / 2 * dp[i][0][0] + bm[i] * dp[i][1][0]) %= M;
}
r = (r + M - dp[d][1][1]) % M;
print(r);
}
return 0;
}

清华集训 2016 求和

$f$ 是一个次数不超过 $m$ 的多项式,满足 $f(i) = a_i, i = 0, \cdots, m$,求

给定 $n, m, x$ 和 $a_0, \cdots, a_m$,其中 $1 \leq n \leq 10^9, 1 \leq m \leq 2 \cdot 10^4, 0 \leq a_i, x \leq 998244353$

做法:利用二项式反演,记 $f(k) = \sum_{i = 0}^k {k \choose i} f_i$,则 $f_k = \sum_{i = 0}^k {k \choose i} (-1)^{k-i} f(i)$ 注意到 $f(x)$ 是次数不超过 $m$ 的多项式,所以 $f_{m + 1} = f_{m + 2} = \cdots = 0$

上述式子最后一项,只有 $m + 1$ 项,$f_0, \cdots, f_m$ 可以由 NFT 计算。

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
83
84
85
86
87
88
89
90
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;

const int N = 2e4 + 2;
const LL M = 998244353;
const LL ROOT = 3;
LL powMod(LL x, LL n) {
LL r(1);
while (n) {
if (n & 1) r = r * x % M;
n >>= 1;
x = x * x % M;
}
return r;
}
LL fac[N], ifac[N];
void init() {
fac[0] = 1;
for (int i = 1; i < N; ++i) fac[i] = fac[i - 1] * i % M;
ifac[N - 1] = powMod(fac[N - 1], M - 2);
for (int i = N - 1; i; --i) ifac[i - 1] = ifac[i] * i % M;
}
void bitreverse(std::vector<LL> &a) {
for (int i = 0, j = 0; i != a.size(); ++i) {
if (i > j) std::swap(a[i], a[j]);
for (int l = a.size() >> 1;
(j ^= l) < l; l >>= 1);
}
}
void nft(std::vector<LL> &a, bool isInverse = false) {
LL g = powMod(ROOT, (M - 1) / a.size());
if (isInverse) {
g = powMod(g, M - 2);
LL invLen = powMod(LL(a.size()), M - 2);
for (auto & x: a) x = x * invLen % M;
}
bitreverse(a);
std::vector<LL> w(a.size(), 1);
for (int i = 1; i != w.size(); ++i) w[i] = w[i - 1] * g % M;
auto addMod = [](LL x, LL y) {
return (x += y) >= M ? x -= M : x;
};
for (int step = 2, half = 1; half != a.size(); step <<= 1, half <<= 1) {
for (int i = 0, wstep = a.size() / step; i != a.size(); i += step) {
for (int j = i; j != i + half; ++j) {
LL t = (a[j + half] * w[wstep * (j - i)]) % M;
a[j + half] = addMod(a[j], M - t);
a[j] = addMod(a[j], t);
}
}
}
}
void mul(std::vector<LL>& a, std::vector<LL> b) {
int sz = 1, tot = a.size() + b.size() - 1;
while (sz < tot) sz *= 2;
a.resize(sz);
b.resize(sz);
nft(a);
nft(b);
for (int i = 0; i != sz; ++i) a[i] = a[i] * b[i] % M;
nft(a, 1);
a.resize(tot);
}

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
init();
int m, n, x;
std::cin >> n >> m >> x;
std::vector<LL> a(m + 1), b(m + 1);
for (int i = 0; i <= m; ++i) {
std::cin >> a[i];
a[i] = ifac[i] * a[i] % M;
b[i] = (i % 2 == 0) ? ifac[i] : M - ifac[i];
}
mul(a, b);
a.resize(m + 1);
LL r = 0, Anx = 1;
for (int i = 0; i <= m; ++i) {
r += Anx * a[i] % M;
Anx = Anx * (n - i) % M * x % M;
}
print(r % M);
return 0;
}

ZOJ 3820:树的重心 + 直径

题意:在一棵树上选择两个点,使得任意点到这两点的最小值的最大值最小。

如果仅仅选择一个点,那么这个问题就是树的重心。

对此情况,将直径按照中点切开,然后分别求两颗子树的中心, 参考资料

证明:如果 u,v 是满足条件的两个点,那么把他们沿着中间切开必然最优,然后反证它们在树的直径上。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas;
std::cin >> cas;
while (cas--) {
int n;
std::cin >> n;
std::vector<std::vector<int>> e(n);
for (int i = 1, u, v; i < n; ++i) {
std::cin >> u >> v;
--u; --v;
e[u].emplace_back(v);
e[v].emplace_back(u);
}
std::vector<int> d(n);
auto bfs = [&](int x) -> int {
std::fill(d.begin(), d.end(), -1);
std::queue<int> Q;
d[x] = 0;
Q.push(x);
while (!Q.empty()) {
int u = Q.front();
Q.pop();
for (auto v : e[u]) if (d[v] == -1) {
d[v] = d[u] + 1;
Q.push(v);
}
}
return std::max_element(d.begin(), d.end()) - d.begin();
};
auto f = [&](int v) {
std::vector<int> a;
a.emplace_back(v);
while (d[a.back()]) {
for (auto v: e[a.back()]) if (d[v] + 1 == d[a.back()]) {
a.emplace_back(v);
break;
}
}
int mid = a.size() / 2;
return std::make_pair(a[mid - 1], a[mid]);
};
auto [u, v] = f(bfs(bfs(0)));
e[u].erase(std::find(e[u].begin(), e[u].end(), v));
e[v].erase(std::find(e[v].begin(), e[v].end(), u));

int ru = bfs(bfs(u)), ansu = (d[ru] + 1) / 2;
auto ra = f(ru).second;
int rv = bfs(bfs(v)), ansv = (d[rv] + 1) / 2;
auto rb = f(rv).second;
std::cout << std::max(ansu, ansv) << " " << ru + 1 << " " << rv + 1 << std::endl;
}
return 0;
}

1422D:堆优化 Dijkstra 复杂度 $O(n \log E)$

这里的边数 E 针对本问题可以大大优化,代码可参考 Jiangly 的代码

1422C

一开始读题读错了,少看了一个连续导致浪费了很多时间(30 分钟)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;
const LL M = 1e9 + 7;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::string a;
std::cin >> a;
LL r = 0, n = 0, id = 1, r2 = 0;
for (auto x : a) {
r = (r * 10 + id * (id - 1) / 2 * (x - '0') + r2) % M;
n = (n * 10 + (x - '0')) % M;
r2 += n;
++id;
}
print(r);
return 0;
}

Atcoder ARC 104D

E, F 题也是好题,不过不懂。

问题描述:对每一个 $m \in [1, n]$ 求满足 $\sum_{x \in S} (x - m) = 0$ 的集合 $S$ 的个数,其中 “集合” $S$ 是由 1~n 中元素构成,元素可重,重数不超过 k。这等价于说 $\sum_{x \in S} x = \sum_{x \in T} x$ 的个数乘以 $k + 1$,其中 $S$ 是由 1 ~ m - 1 构成,$T$ 由 1 ~ n - m 构成。

做法:我们设 dp[i][j] 表示仅用 1 ~ i 中的数构成和为 j 的个数,那么显然

于是我们保存一下前缀和,那么就可以优化计算了。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, k;
LL M;
std::cin >> n >> k >> M;
std::vector<std::vector<LL>> dp(n);
dp[0].emplace_back(1);
for (int i = 1; i < n; ++i) {
int mx = (i + 1) * i / 2 * k;
dp[i] = dp[i - 1];
dp[i].resize(mx + 1);
for (int j = i; j <= mx; ++j) dp[i][j] += dp[i][j - i];
for (int j = mx; j >= (k + 1) * i; --j) dp[i][j] -= dp[i][j - (k + 1) * i] %= M;
for (auto &x : dp[i]) x %= M;
}
for (int x = 1; x <= n; ++x) {
LL r = 0;
int tx = std::min(x - 1, n - x);
for (int j = (tx + 1) * tx / 2 * k; j >= 0; --j) r += dp[x - 1][j] * dp[n - x][j] % M;
r = (r * (k + 1) % M + M - 1) % M;
print(r);
}
return 0;
}

1408D:二维处理问题,经典重要问题

一开始没有看数据范围觉得没法过,就没想这个问题,然后狮子大张口想做最后一题…

此问题可以转换成经典问题:二维平面,第一象限的 $n$ 个点,每次只能同时向左或者向下移动一个单位,问最少需要多少步,让所有的点都不在第一象限。
首先先对一个坐标(例如横坐标)进行从小到大排序,然后最终结果就是 第 i 个点的横坐标加上后面所有点的纵坐标的最大值,这 n + 1 个结果中最小的一个!

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m;
std::cin >> n >> m;
std::vector<std::pair<int, int>> a(n), b(m), t;
for (auto &[x, y] : a) std::cin >> x >> y;
for (auto &[x, y] : b) std::cin >> x >> y;
std::vector<int> r(n);
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (a[i].first <= b[j].first && a[i].second <= b[j].second) {
t.push_back({b[j].first - a[i].first + 1, b[j].second - a[i].second + 1});
}
}
}
std::sort(t.begin(), t.end());
std::vector<int> mx(t.size() + 1);
for (int i = t.size() - 1; ~i; --i) {
mx[i] = std::max(mx[i + 1], t[i].second);
}
int ans = mx[0];
for (int i = 0; i < t.size(); ++i) {
ans = std::min(ans, mx[i + 1] + t[i].first);
}
print(ans);
return 0;
}

1408F:脑筋急转弯问题

读完题目我就知道对于 $n$ 为 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
29
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<std::pair<int, int>> r;
auto f = [&](int x, int len) {
for (int step = 1; step < len; step *= 2) {
for (int i = 0; i < len; i += step * 2) {
for (int j = 0; j < step; ++j) {
r.push_back({i + j + x, i + j + x + step});
}
}
}
};
int k = std::__lg(n);
f(1, 1 << k);
f(n + 1 - (1 << k), 1 << k);
print(r.size());
for (auto [x, y] : r) std::cout << x << " " << y << std::endl;
return 0;
}

1408I

本题解基于 Soulist 的题解和 Jiangly 的代码,写这个的原因是这个题我是非常想搞清楚,然后只有转化成自己的语言才能弄清楚,所以想写下来,从而说明是真的理解了。

题意:给定 $n$ 个数 ($a_1, a_2, \cdots, a_n$),(序列元素互异,但是这个条件没啥用吧)每次操作为等概率的选择其中一个数,然后将其减一,问经过 $k$ 次操作之后这 $n$ 个数的异或和 为 $x = 0, 1, \cdots, 2^c - 1$ 的概率。

其中, $k, c \leq 16, a_i \in [k, 2^c)$,最后答案是分数,在模 998244353 的意义下就是个整数啦。

  • 令 $sa = a_1 \oplus a_2 \cdots \oplus a_n$,然后考虑答案在 $t$ 上的改变量。
  • 令 $d_{i,j} = a_i \oplus (a_i - j)$,$\displaystyle F(x, y) = \prod_{i = 1}^n \left( \sum_{j = 0}^k \frac{x^{d_{i,j}}}{j!} y^j \right)$ 那么我们的答案就是 $F(x,y)[y^k] \cdot k!$,其中 $x$ 上是异或卷积,$y$ 上的是普通卷积。

那么我们现在主要问题就变成如何就 $F(x,y)[y^k] \cdot k!$ 了。由于 $x$ 上的系数加法是异或加法,所以我们需要用 异或的 fwt 将它转化成普通乘法。

  • $x$ 上的系数可以本质是下标!下标对应的值是关于$y$的多项式,首先注意到做 fwt 仅仅是将 $x$ 上的系数做了改变,并没有改变其它内容。这样做是为了把卷积异或乘法改成普通卷积乘法。这里做 fwt 的时候可以暴力搞。
  • 优化 1:仅有 $O(ck)$ 中 $k$ 元组
  • 每个 $k$ 元组对应的下标对应 fwt 之后的编号只有 $2^k$ 种!
  • 多项式乘法可以用 ln/exp 来运算

soulist 写了题解
以下是 jiangly 大佬的代码 的注释版。

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
83
84
85
86
// code by jiangly
#include <bits/stdc++.h>
constexpr int P = 998244353;
int power(int a, int b) {
int res = 1;
for (; b > 0; b /= 2, a = 1ll * a * a % P)
if (b % 2 == 1) res = 1ll * res * a % P;
return res;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);

int n, k, c;
std::cin >> n >> k >> c;
std::vector<int> fac(k + 1), invFac(k + 1), inv(k + 1);
fac[0] = 1;
for (int i = 1; i <= k; ++i) fac[i] = 1ll * fac[i - 1] * i % P;
for (int i = 0; i <= k; ++i) invFac[i] = power(fac[i], P - 2);
for (int i = 1; i <= k; ++i) inv[i] = power(i, P - 2);
// 预处理出所有元素异或和,以及每一种序列的个数
std::map<std::vector<int>, int> cnt;
int xsum = 0;
for (int i = 0; i < n; ++i) {
int a;
std::cin >> a;
xsum ^= a;
std::vector<int> d(k + 1);
for (int j = 0; j <= k; ++j) d[j] = a ^ (a - j);
++cnt[d];
}
// cntm 就是暴力做 fwt 后的每种序列的个数
// f 就是做完 fwt 后得到式子乘积。
std::vector<int> f(1 << c), cntm(1 << k);
for (int x = 0; x < (1 << c); ++x) {
std::vector<int> e;
for (auto [d, t] : cnt) {
int mask = 0;
for (int i = 1; i <= k; ++i) if (__builtin_parity(x & d[i])) mask |= 1 << (i - 1);
if (cntm[mask] == 0) e.push_back(mask);
cntm[mask] += t;
}
std::vector<int> g(k + 1);
g[0] = 1;
for (auto mask : e) {
std::vector<int> a(k + 1);
a[0] = 1;
for (int i = 1; i <= k; ++i) {
if (mask >> (i - 1) & 1) a[i] = P - invFac[i];
else a[i] = invFac[i];
}
std::vector<int> pw(k + 1);
int t = cntm[mask];
pw[0] = 1;
// 取对数, 再做加法
for (int i = 1; i <= k; ++i) {
int res = 0;
for (int j = 0; j < i; ++j) res = (res + 1ll * pw[j] * a[i - j] % P * (i - j)) % P;
res = 1ll * res * t % P;
for (int j = 1; j < i; ++j) res = (res + 1ll * (P - a[j]) * pw[i - j] % P * (i - j)) % P;
pw[i] = 1ll * res * inv[i] % P;
}
// 加起来取指数
for (int i = k; i >= 1; --i) {
int res = 0;
for (int j = 0; j <= i; ++j) res = (res + 1ll * pw[j] * g[i - j]) % P;
g[i] = res;
}
cntm[mask] = 0;
}
f[x] = 1ll * g[k] * fac[k] % P;
}
// ifwt 得到最后的答案,注意到这里没有除以 2,是因为它最后整体除了 2^c。
for (int i = 1; i < (1 << c); i *= 2) {
for (int j = 0; j < (1 << c); j += 2 * i) {
for (int k = 0; k < i; ++k) {
int u = f[j + k], v = f[i + j + k];
f[j + k] = (u + v) % P;
f[i + j + k] = (u - v + P) % P;
}
}
}
int invn = 1ll * power(1 << c, P - 2) * power(n, P - 1 - k) % P;
for (int i = 0; i < (1 << c); ++i) std::cout << 1ll * f[i ^ xsum] * invn % P << " \n"[i == (1 << c) - 1];
return 0;
}

453B:状态压缩 DP

给定数列 $a$,求满足元素两两互素的数列 $b$ 使得 $\sum |a_i - b_i|$ 最小

注意到 $b_i < 2 a_i$,因为否则取 $b_i = 1$ 即可。

由于 $60$ 内的素数个数为 17, 因此可以状态压缩 DP。设 dp[i][j] 表示使得 $\sum_{k = 1} ^ i |a_k - b_k|$ 最小,且$b_1 \cdots b_i$ 中所有出现的素因子的状态为 $j$。因此状态转移就是 dp[i][j | factor[k]] = min(dp[i - 1][j] + |a_i - k|),其中 factor[k] 与 $j$ 没有交集。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<int> a(n);
for (auto &x : a) std::cin >> x;
int ma = *std::max_element(a.begin(), a.end()) * 2;
std::vector<int> p;
for (int i = 2; i < ma; ++i) {
bool flag = true;
for (int j = 2; j * j <= i; ++j) if (i % j == 0) {
flag = false;
break;
}
if (flag) p.emplace_back(i);
}
std::vector<int> factor(ma);
for (int i = 2; i < ma; ++i) {
for (int j = 0; j < p.size(); ++j) if (i % p[j] == 0) {
factor[i] |= (1 << j);
}
}
std::vector<std::vector<int>> ans(n + 1, std::vector<int>(1 << p.size(), 1e9));
ans[0][0] = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j < 2 * a[i - 1]; ++j) {
for (int k = 0; k < (1 << p.size()); ++k) if ((k & factor[j]) == 0) {
ans[i][k | factor[j]] = std::min(ans[i][k | factor[j]], ans[i - 1][k] + abs(a[i - 1] - j));
}
}
}
std::vector<int> r(n);
int now = std::min_element(ans[n].begin(), ans[n].end()) - ans[n].begin();
for (int i = n - 1; i >= 0; --i) {
for (int j = 1; j < 2 * a[i]; ++j) if ((now | factor[j]) == now) {
if (ans[i][now ^ factor[j]] + abs(a[i] - j) == ans[i + 1][now]) {
r[i] = j;
now ^= factor[j];
break;
}
}
}
for (auto x : r) std::cout << x << " ";
println;
return 0;
}

662C:状态压缩 DP + FWT 模板

给定 $n \times m$ 的 0-1 方阵,可以取反一些行和列使得最后 0 的数列最小。

首先注意到 $n < 20$,我们可以把每一列看作一个状态 i ,并且结果跟列的顺序无关。我们可以记录下初始情况每种状态数 C[i] 量。
并且每一种状态 i 对答案的贡献显然就是它的 0, 1 个数的最小值记作 g[i]
对于每一个行取反 S, 其实就是将一个状态 i 变成 状态 i ^ S
所以每一种行取反 S,最终的答案 $\displaystyle F(S) = \sum_{i} C[i] \cdot g[i \wedge S] = \sum_{i \wedge j = S} C[i] \cdot g[j]$

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl
using LL = long long;

#include <cstdio>
#include <algorithm>
#include <vector>

const int P = 998244353;

void add(int &x, int y) {
(x += y) >= P && (x -= P);
}
void sub(int &x, int y) {
(x -= y) < 0 && (x += P);
}
struct FWT {
int extend(int n) {
int N = 1;
for (; N < n; N <<= 1);
return N;
}
void FWTor(std::vector<int> &a, bool rev) {
int n = a.size();
for (int l = 2, m = 1; l <= n; l <<= 1, m <<= 1) {
for (int j = 0; j < n; j += l) for (int i = 0; i < m; i++) {
if (!rev) add(a[i + j + m], a[i + j]);
else sub(a[i + j + m], a[i + j]);
}
}
}
void FWTand(std::vector<int> &a, bool rev) {
int n = a.size();
for (int l = 2, m = 1; l <= n; l <<= 1, m <<= 1) {
for (int j = 0; j < n; j += l) for (int i = 0; i < m; i++) {
if (!rev) add(a[i + j], a[i + j + m]);
else sub(a[i + j], a[i + j + m]);
}
}
}
void FWTxor(std::vector<int> &a, bool rev) {
int n = a.size(), inv2 = (P + 1) >> 1;
for (int l = 2, m = 1; l <= n; l <<= 1, m <<= 1) {
for (int j = 0; j < n; j += l) for (int i = 0; i < m; i++) {
int x = a[i + j], y = a[i + j + m];
if (!rev) {
a[i + j] = (x + y) % P;
a[i + j + m] = (x - y + P) % P;
} else {
a[i + j] = 1LL * (x + y) * inv2 % P;
a[i + j + m] = 1LL * (x - y + P) * inv2 % P;
}
}
}
}
std::vector<int> Or(std::vector<int> a1, std::vector<int> a2) {
int n = std::max(a1.size(), a2.size()), N = extend(n);
a1.resize(N), FWTor(a1, false);
a2.resize(N), FWTor(a2, false);
std::vector<int> A(N);
for (int i = 0; i < N; i++) A[i] = 1LL * a1[i] * a2[i] % P;
FWTor(A, true);
return A;
}
std::vector<int> And(std::vector<int> a1, std::vector<int> a2) {
int n = std::max(a1.size(), a2.size()), N = extend(n);
a1.resize(N), FWTand(a1, false);
a2.resize(N), FWTand(a2, false);
std::vector<int> A(N);
for (int i = 0; i < N; i++) A[i] = 1LL * a1[i] * a2[i] % P;
FWTand(A, true);
return A;
}
std::vector<int> Xor(std::vector<int> a1, std::vector<int> a2) {
int n = std::max(a1.size(), a2.size()), N = extend(n);
a1.resize(N), FWTxor(a1, false);
a2.resize(N), FWTxor(a2, false);
std::vector<int> A(N);
for (int i = 0; i < N; i++) A[i] = 1LL * a1[i] * a2[i] % P;
FWTxor(A, true);
return A;
}
} fwt;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m;
std::cin >> n >> m;
std::vector<std::string> a(n);
for (auto &x : a) std::cin >> x;
std::vector<int> c(1 << n), g(1 << n);
for (int i = 0; i < m; ++i) {
int r = 0;
for (int j = 0; j < n; ++j) {
r |= (a[j][i] - '0') << j;
}
++c[r];
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < (1 << n); ++j) {
if (j & (1 << i)) ++g[j];
}
}
for (int i = 0; i < (1 << n); ++i) {
g[i] = std::min(g[i], n - g[i]);
}
auto f = fwt.Xor(c, g);
print(*std::min_element(f.begin(), f.end()));
return 0;
}

1417E:异或问题和逆序数

注意到两个数比较大小只和它最高位数字有关,如果改变第 $i$ 位,那么第 $i$ 位后面的数就可以不考虑。
可以参考 jiangly 的代码

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
using LL = long long;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<int> a(n);
for (auto &x : a) std::cin >> x;
int r = 0;
LL cnt = 0;
for (int d = 1; d < 31; ++d) {
std::vector<int> p(n);
std::iota(p.begin(), p.end(), 0);
std::sort(p.begin(), p.end(),[&](int i, int j) {
return (a[i] >> d) < (a[j] >> d) || ((a[i] >> d) == (a[j] >> d) && i < j);
});
LL c0 = 0, c1 = 0;
for (int i = 0, j = 0; i < n; i = j) {
int x0 = 0, x1 = 0;
while (j < n && (a[p[i]] >> d) == (a[p[j]] >> d)) {
if ((a[p[j]] >> (d - 1)) & 1) {
++x1;
c0 += x0;
} else {
++x0;
c1 += x1;
}
++j;
}
}
if (c0 < c1) {
cnt += c0;
r |= (1 << d - 1);
} else cnt += c1;
}
std::cout << cnt << " " << r << std::endl;
return 0;
}

上述过程也提供了一种求逆序数的方法,就是注意到任意两个数,他们总会在某一个最高位是一致的。

AtCoder ACL2f: 包容排斥原理 + NFT

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl;
using LL = long long;

const LL M = 998244353, ROOT = 3;
LL powMod(LL x, LL n) {
LL r(1);
while (n) {
if (n & 1) r = r * x % M;
n >>= 1;
x = x * x % M;
}
return r;
}
void bitreverse(std::vector<LL> &a) {
for (int i = 0, j = 0; i != a.size(); ++i) {
if (i > j) std::swap(a[i], a[j]);
for (int l = a.size() >> 1;
(j ^= l) < l; l >>= 1);
}
}
void nft(std::vector<LL> &a, bool isInverse = false) {
LL g = powMod(ROOT, (M - 1) / a.size());
if (isInverse) {
g = powMod(g, M - 2);
LL invLen = powMod(LL(a.size()), M - 2);
for (auto & x: a) x = x * invLen % M;
}
bitreverse(a);
std::vector<LL> w(a.size(), 1);
for (int i = 1; i != w.size(); ++i) w[i] = w[i - 1] * g % M;
auto addMod = [](LL x, LL y) {
return (x += y) >= M ? x -= M : x;
};
for (int step = 2, half = 1; half != a.size(); step <<= 1, half <<= 1) {
for (int i = 0, wstep = a.size() / step; i != a.size(); i += step) {
for (int j = i; j != i + half; ++j) {
LL t = (a[j + half] * w[wstep * (j - i)]) % M;
a[j + half] = addMod(a[j], M - t);
a[j] = addMod(a[j], t);
}
}
}
}
void mul(std::vector<LL>& a, std::vector<LL> b) {
int sz = 1, tot = a.size() + b.size() - 1;
while (sz < tot) sz *= 2;
a.resize(sz);
b.resize(sz);
nft(a);
nft(b);
for (int i = 0; i != sz; ++i) a[i] = a[i] * b[i] % M;
nft(a, 1);
a.resize(tot);
}
const int N = 1e5 + 2;
LL fac[N], ifac[N];
LL inv(LL a){ // 0 < a < p and gcd(a,p) = 1
return a == 1 ? 1 : (M - M / a) * inv(M % a) % M;
}
void init() {
fac[0] = ifac[0] = 1;
for (int i = 1; i < N; ++i) fac[i] = fac[i - 1] * i % M;
ifac[N - 1] = inv(fac[N - 1]);
for (int i = N - 1; i; --i) ifac[i - 1] = ifac[i] * i % M;
}
LL binom(int n, int k) {
if (n < k) return 0;
return fac[n] * ifac[k] % M * ifac[n - k] % M;
}
int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
init();
int n;
std::cin >> n;
std::vector<int> cnt(1e5 + 2);
for (int i = 0, x; i < 2 * n; ++i) {
std::cin >> x;
++cnt[x];
}
std::vector<LL> r(1, 1);
LL inv2 = (M + 1) / 2;
std::sort(cnt.begin(), cnt.end());
auto start = std::lower_bound(cnt.begin(), cnt.end(), 1) - cnt.begin();
std::vector<std::pair<int, int>> q;
q.push_back({cnt[start], 1});
for (int i = start + 1; i < cnt.size(); ++i) {
if(cnt[i] != q.back().first) q.push_back({cnt[i], 1});
else ++q.back().second;
}
for (auto [x, xn] : q) if (x > 0) {
std::vector<LL> a(x / 2 + 1);
LL p2j = 1;
for (int j = 0; j <= x / 2; ++j) {
a[j] = fac[x] * ifac[x - 2 * j] % M * ifac[j] % M * p2j % M;
p2j = p2j * inv2 % M;
}
while (xn) {
if (xn & 1) mul(r, a);
xn >>= 1; mul(a, a);
}
}
std::vector<LL> G(n + 1);
G[0] = 1;
for (int i = 1; i <= n; ++i) G[i] = G[i - 1] * (2 * i - 1) % M;
LL ret = 0;
for (int i = 0; i < r.size() && i <= n; ++i) {
ret += (i % 2 == 1 ? -1 : 1) * G[n - i] * r[i] % M;
}
print((ret % M + M) % M);
return 0;
}

AtCoder ACL2e:线段树模板题

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>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
#define println std::cout << std::endl;
using LL = long long;
#define lrt rt << 1
#define rrt rt << 1 | 1
#define lson l, m, lrt
#define rson m + 1, r, rrt
const int N = 2e5 + 5;
const int NN = N * 3.2;
LL sum[NN], s10[N];
int col[NN];
const LL M = 998244353;
void pushUp(int rt) {
sum[rt] = sum[lrt] + sum[rrt];
if (sum[rt] >= M) sum[rt] -= M;
}
void pushDown(int l,int r, int rt) {
if (col[rt] != -1) {
int m = (l + r) >> 1;
col[lrt] = col[rrt] = col[rt];
col[rt] = -1;
sum[lrt] = (s10[m] - s10[l - 1] + M) * col[lrt] % M;
sum[rrt] = (s10[r] - s10[m] + M) * col[rrt] % M;
}
}
void build(int l, int r, int rt) {
if (l == r) {
sum[rt] = s10[r] - s10[l - 1];
if (sum[rt] < 0) sum[rt] += M;
col[rt] = 1;
return;
}
col[rt] = 1;
int m = (l + r) >> 1;
build(lson);
build(rson);
pushUp(rt);
}
void update(int L, int R, int p, int l, int r, int rt) {
if (L <= l && R >= r) {
sum[rt] = (s10[r] - s10[l - 1] + M) * p % M;
col[rt] = p;
return;
}
pushDown(l, r, rt);
int m = (l + r) >> 1;
if (L <= m) update(L, R, p, lson);
if (R > m) update(L, R, p, rson);
pushUp(rt);
}

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
s10[0] = 0;
LL now = 1;
for (int i = 1; i < N; ++i) {
s10[i] = now;
now = now * 10 % M;
}
for (int i = 1; i < N; ++i) {
s10[i] += s10[i - 1];
if (s10[i] >= M) s10[i] -= M;
}
int n, q;
std::cin >> n >> q;
build(1, n, 1);
while (q--) {
int l, r, d;
std::cin >> l >> r >> d;
update(n - r + 1, n - l + 1, d, 1, n, 1);
print(sum[1]);
}
return 0;
}

AtCoder ABC178e:Manhattan 距离

给定一堆点,求它们的 Manhattan 距离中最大值

注意到其实任意两点的距离等于,它们到最左上角和到最下角的距离的距离差的最大值。

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>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
using LL = long long;
const LL M = 1e9 + 7;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
int x[n], y[n];
for (int i = 0; i < n; ++i) {
std::cin >> x[i] >> y[i];
}
int xx[2] = {0, 0}, yy[2] = {0, int(1e9)};
int t[2][n];
for (int id = 0 ; id < 2; ++id) {
for (int i = 0; i < n; ++i) {
t[id][i] = abs(x[i] - xx[id]) + abs(y[i] - yy[id]);
}
}
int r0 = *std::max_element(t[0], t[0] + n) - *std::min_element(t[0], t[0] + n);
int r1 = *std::max_element(t[1], t[1] + n) - *std::min_element(t[1], t[1] + n);
print(std::max(r0, r1));
return 0;
}

1406D:经典数学题

给定数列 $a$, 求单调递增数列 $b$ 和单调递减数列 $c$,使得 $a_i = b_i + c_i$,并且使得 $b, c$ 中的最大值最小(即 $\max(b_n, c_1)$ 最小。

如果 $a_i < a_{i+1}$ 令 $b_{i +1} = b_i + a_{i + 1} - a_i$,否则 $c_{i + 1} = c_i + a_{i + 1} - a_i$。
设 $c_1 = x$,$b_ 1 = a - x$,容易看出 $b_n = \sum_{i = 2}^n \max{0, a_{i} - a_{i-1}}$。并且每次更新只和 $l, r$ 节点有关。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
using LL = long long;

LL floor(LL a, LL n) { // n > 0
return a < 0 ? (a - n + 1) / n : a / n;
}

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
LL a[n], ret = 0;
for (int i = 0; i < n; ++i) std::cin >> a[i];
for (int i = n - 1; i; --i) a[i] -= a[i - 1];
for (int i = 1; i < n; ++i) ret += std::max(0LL, a[i]);

std::cout << floor(a[0] + ret + 1, 2) << std::endl;
int q;
std::cin >> q;
while (q--) {
int l, r, x;
std::cin >> l >> r >> x;
if (r != n) {
ret -= std::max(0LL, a[r]);
a[r] -= x;
ret += std::max(0LL, a[r]);
}
if (l != 1) ret -= std::max(0LL, a[l - 1]);
a[l - 1] += x;
if (l != 1) ret += std::max(0LL, a[l - 1]);
std::cout << floor(a[0] + ret + 1, 2) << std::endl;
}
return 0;
}

1407B:GCD 问题

如果数据范围特别大,则需要下面的处理方式,否则其实可以预处理 gcd
如果这题 $c_i = \gcd(b_i, b_{i + 1}$ 将会超级难!

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
using LL = long long;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas;
std::cin >> cas;
while (cas--) {
int n;
std::cin >> n;
int a[n];
bool v[n] = {};
for (int i = 0; i < n; ++i) std::cin >> a[i];
int id = std::max_element(a, a + n) - a;
std::vector<int> ans;
ans.push_back(a[id]);
v[id] = 1;
int now = a[id];
while (ans.size() != n) {
int mx = 0, mi = 1e9 + 2;
for (int i = 0; i < n; ++i) if (!v[i]) {
mx = std::max(mx, std::__gcd(now, a[i]));
mi = std::max(mi, std::__gcd(now, a[i]));
}
if (mx == mi) break;
for (int i = 0; i < n; ++i) if (!v[i]) {
if(mx == std::__gcd(now, a[i])) {
ans.push_back(a[i]);
v[i] = 1;
}
}
now = mx;
}
for (auto x : ans) std::cout << x << " ";
for (int i = 0; i < n; ++i) if (!v[i]) std::cout << a[i] << " ";
std::cout << std::endl;
}
return 0;
}

1407C:第一次过真实的交互问题

注意到 $(a \mod b )> (b \mod a)$ 当且仅当 $a < b$ (这里 $a, b$ 都是正整数),并且此时 $(a \mod b) = a$

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
using LL = long long;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
int a[n + 1], mi = 1;
for (int i = 2, mx1, mx2; i <= n; ++i) {
std::cout << "? " << i << " " << mi << std::endl;
std::cin >> mx1;
std::cout << "? " << mi << " " << i << std::endl;
std::cin >> mx2;
if (mx1 > mx2) {
a[i] = mx1;
} else {
a[mi] = mx2;
mi = i;
}
}
a[mi] = n;
std::cout << "!";
for (int i = 1; i <= n; ++i) std::cout << " " << a[i];
std::cout << std::endl;
return 0;
}

1407D:经典问题:单调栈优化 DP

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
using LL = long long;

int main() {
//freopen("in", "r", stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<int> h(n), dp(n);
for (auto &x : h) std::cin >> x;
std::stack<int> low, high;
for (int i = 0; i < n; ++i) {
dp[i] = (i == 0 ? 0 : dp[i - 1] + 1);
while (!low.empty() && h[i] > h[low.top()]) {
dp[i] = std::min(dp[i], dp[low.top()] + 1);
low.pop();
}
if (!low.empty()) dp[i] = std::min(dp[i], dp[low.top()] + 1);
if (!low.empty() && h[i] == h[low.top()]) {
low.top() = i;
} else low.push(i);

while (!high.empty() && h[i] < h[high.top()]) {
dp[i] = std::min(dp[i], dp[high.top()] + 1);
high.pop();
}
if (!high.empty()) dp[i] = std::min(dp[i], dp[high.top()] + 1);
if (!high.empty() && h[i] == h[high.top()]) {
high.top() = i;
} else high.push(i);
}
std::cout << dp[n - 1] << std::endl;
return 0;
}

1405D:树的直径

$A, B$ 分别在树上某两点,每次移动的距离最大值为 $da, db$,$A$ 先移动。
如果 $A$ 有策略在有限步后与 $B$ 在同一点,那么就 $A$ 获胜,否则 $B$ 获胜。

$A$ 胜可以分这三种情况:

  • $A$ 直接能到 $B$
  • 两倍 $da$ 大于或等于 树的直径
  • 两倍 $da$ 大于或等于 $db$
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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
using LL = long long;
class LinkStar {
public:
std::vector<int> head, nxt, to;
LinkStar(int n) {
nxt.clear();
to.clear();
head = std::vector<int>(n + 1, -1);
}
void addedge(int u, int v) {
nxt.emplace_back(head[u]);
head[u] = to.size();
to.emplace_back(v);
}
};

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas;
std::cin >> cas;
while (cas--) {
int n, a, b, da, db;
std::cin >> n >> a >> b >> da >> db;
LinkStar A(n);
for (int i = 1, u, v; i < n; ++i) {
std::cin >> u >> v;
A.addedge(u, v);
A.addedge(v, u);
}
std::vector<int> d(n + 1);
auto bfs = [&](int s) {
std::fill(d.begin(), d.end(), -1);
std::queue<int> q;
q.push(s);
d[s] = 0;
while (!q.empty()) {
int u = q.front();
q.pop();
for (int i = A.head[u]; ~i; i = A.nxt[i]) {
int v = A.to[i];
if (d[v] == -1) {
d[v] = d[u] + 1;
q.push(v);
}
}
}
return std::max_element(d.begin(), d.end()) - d.begin();
};
int c = bfs(a);
if (db <= 2 * da || d[b] <= da) {
std::cout << "Alice\n";
} else {
c = bfs(c);
std::cout << (d[c] <= 2 * da ? "Alice" : "Bob") << std::endl;
}
}
return 0;
}

1405E:高端树状数组

官方题解

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
using LL = long long;
struct TreeArray {
std::vector<LL> s;
TreeArray() {}
TreeArray(int n) { init(n); }
void init(int n) {
s.resize(n + 1);
std::fill(s.begin(), s.end(), 0);
}
int lowbit(int n) {
return n & (-n);
}
void add(int id, int p) {
while (id < s.size()) {
s[id] += p;
id += lowbit(id);
}
}
LL sum(int id) {
LL r = 0;
while (id) {
r += s[id];
id -= lowbit(id);
}
return r;
}
// find maximal index s.t. sum(id) >= x, sum must be decreased
int search(LL val, int r) {
LL sum = 0;
if (s[1] < val) return 0;
int id = 0;
for (int i = std::__lg(r); ~i; --i) {
if (id + (1 << i) < r && sum + s[id + (1 << i)] >= val) {
id += (1 << i);
sum += s[id];
}
}
return id;
}
};
struct TreeArray2 {
int n;
TreeArray B, C;
TreeArray2() {}
TreeArray2(int _n) : n(_n){
B.init(n);
C.init(n);
}
void add(int id, int p) {
C.add(id, p);
B.add(id, (id - 1) * p);
}
void add(int l, int r, int p) {
add(l, p);
if (r + 1 < n) add(r + 1, -p);
}
LL sum (int id) {
return id * C.sum(id) - B.sum(id);
}
};

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, q;
std::cin >> n >> q;
std::vector<int> a(n + 1), ans(q), left(q);
std::vector<std::vector<int>> right(n + 1);
for (int i = 1; i <= n; ++i) {
std::cin >> a[i];
a[i] = i - a[i];
}
for (int i = 0, x, y; i < q; ++i) {
std::cin >> x >> y;
left[i] = 1 + x;
right[n - y].emplace_back(i);
}
TreeArray2 A(n);
for (int r = 1; r <= n; ++r) {
if (a[r] >= 0) {
int id = A.C.search(a[r], r + 1);
if (id > 0) A.add(1, std::min(id, r), 1);
}
for (auto x : right[r]) {
ans[x] = A.sum(left[x]);
if (left[x] > 1) ans[x] -= A.sum(left[x] - 1);
}
}
for (int i = 0; i < q; ++i) {
std::cout << ans[i] << std::endl;
}
return 0;
}

1401D:用 dfs 变成有根树,求出每条边的权重,存在第二次到达的节点上,然后给权重高的边赋大值即可

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
using LL = long long;
const LL M = 1e9 + 7;

class LinkStar {
public:
std::vector<int> head, nxt, to;
LinkStar(int n) {
nxt.clear();
to.clear();
head = std::vector<int>(n + 1, -1);
}
void addedge(int u, int v) {
nxt.emplace_back(head[u]);
head[u] = to.size();
to.emplace_back(v);
}
};

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas;
std::cin >> cas;
while (cas--) {
int n;
std::cin >> n;
LinkStar A(n);
for (int i = 1, x, y; i < n; ++i) {
std::cin >> x >> y;
A.addedge(x, y);
A.addedge(y, x);
}
int d[n + 1];
bool vis[n + 1] = {};
std::function<int(int)> dfs = [&](int u) -> int {
vis[u] = true;
d[u] = 1;
for (int i = A.head[u]; ~i; i = A.nxt[i]) {
if (int v = A.to[i]; !vis[v]) {
d[u] += dfs(v);
}
}
return d[u];
};
dfs(1);
std::vector<LL> r;
for (int i = 2; i <= n; ++i) {
r.emplace_back(LL(d[i]) * (n - d[i]));
}
int m;
std::cin >> m;
std::vector<LL> p(m);
for (auto &x : p) std::cin >> x;
if (r.size() > p.size()) {
std::vector<LL> tmp(r.size() - p.size(), 1);
p.insert(p.end(), tmp.begin(), tmp.end());
}
std::sort(r.begin(), r.end());
std::sort(p.begin(), p.end());
LL s = 1;
while (r.size() < p.size()) {
s = s * p.back() % M;
p.pop_back();
}
p.back() = p.back() * s % M;
LL ans = 0;
for (int i = 0; i != r.size(); ++i) {
ans += r[i] * p[i] % M;
}
std::cout << ans % M << std::endl;
}
return 0;
}

1401E

先算左横线段,再算右横线段,左横线段被退出的时候对应位置加 -1 即可,然后根据竖线从左到右依次加入右横线段,剔除左横线段。用树状数组计算一下就可以了。

注意如果横竖直线有触碰到两端,那么答案对应要 +1
注意到如果数据范围很大(例如 N = 1e9)那么可以压缩一下达到目的。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
using LL = long long;
const int N = 1e6;

struct TreeArray {
std::vector<LL> s;
TreeArray() {}
TreeArray(int n) {
s.resize(n);
}
int lowbit(int n) {
return n & (-n);
}
void add(int id, int p) {
while (id < s.size()) {
s[id] += p;
id += lowbit(id);
}
}
LL sum(int id) {
LL r = 0;
while (id) {
r += s[id];
id -= lowbit(id);
}
return r;
}
};

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, m;
std::cin >> n >> m;
std::vector<std::pair<int, int>> la, ra;
for (int i = 0; i < n; ++i) {
int lx, rx, y;
std::cin >> y >> lx >> rx;
if (lx == 0) la.push_back({rx, y});
else ra.push_back({lx, y});
}
sort(la.begin(), la.end());
sort(ra.begin(), ra.end());
std::vector<std::tuple<int, int, int>> b(m);
for (auto &[x, ly, ry] : b) std::cin >> x >> ly >> ry;
sort(b.begin(), b.end());
LL r = 1;
TreeArray A(N + 1);
for (auto &[x, y] : la) {
A.add(y + 1, 1);
if (x == N) ++r;
}
//std::cout << A.sum(100) << std::endl;
int lid = 0, rid = 0;
for (auto &[x, ly, ry] : b) {
while (lid < la.size() && la[lid].first < x) {
A.add(la[lid].second + 1, -1);
++lid;
}
while (rid < ra.size() && ra[rid].first <= x) {
A.add(ra[rid].second + 1, 1);
++rid;
}
r += A.sum(ry + 1);
if (ly != 0) r -= A.sum(ly);
else if (ry == N) ++r;
}
std::cout << r << std::endl;
return 0;
}

HDU 6838 Battle for Wosneth

有两个人记作 Alice 和 Bob,Alice 的生命值很高,所以可以认为是无限的,Bob 的生命值为 m。两个人的攻击命中率分别为 p,q。两个人轮流攻击对方。从 Alice 开始攻击,每次攻击的时候,如果 Alice 命中,那么能让对方的生命值减低 1,同时自己的生命值能恢复 1,如果 Bob 命中,那么能让对方的生命值减低 1,注意 Bob 不会自己回血。直到 Bob 的血量变为 0,游戏结束。Alice 想知道,游戏结束的时候,自己期望生命值。

不妨设,期望为 a[m], 则显然
a[m] = p(1-q)(a[m-1]+1) + pq a[m-1] + (1-p)q(a[m]-1) + (1-p)(1-q)a[m]
化简一下得到 $a_m = a_{m - 1} + \frac{p - q}{p}$. 另外 $a_1 = p + (1 - p) q (a_1 - 1) + (1 - p) (1 - q) a_1$,所以 $a_1 = \frac{p - q + pq}{p}$ 即 $a_m = \frac{p - q}{p} m + q$

HDU 6842 Battle for Wosneth2

有两个人记作 Alice 和 Bob,生命值分别是 n,m,命中率分别为 p%,q%。两个人轮流攻击对方,从 Alice 开始攻击,每次攻击的时候,如果命中,那么能让对方的生命值减低 1,直到一方的生命值不超过 0 为止。问 Alice 胜的概率

a[n][m], b[m][n] 分别表示表示 Alice, Bob 胜的概率。则
a[n][m] = p(1-b[n][m]) + (1-p)(1-b[n][m])
b[n][m] = q(1-a[m-1][n] + (1-q)(1-a[m][n]
所以我们有(p+q-pq) a[n][m] = p(1-q) a[m][n] + (1-p)q a[m-1][n] + pq a[m-1][n-1]

1399D: 01序列分组,使得各组相邻元素不同(主要考察复杂度,超级容易 TLE)

可以存储当前 10 的个数,然后一直跑,就是 $O(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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
using LL = long long;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas;
std::cin >> cas;
while (cas--) {
int n;
std::string s;
std::cin >> n >> s;
int k = 0, r[n];
std::stack<int> id[2];
for (int i = 0; i < n; ++i) {
int t = ('0' == s[i]);
if (id[t].size()) {
r[i] = id[t].top();
id[t].pop();
} else {
r[i] = ++k;
}
id[1 - t].push(r[i]);
}
print(k);
for (int i = 0; i < n; ++i) std::cout << r[i] << " ";
std::cout << std::endl;
}
return 0;
}

1399E1:dfs 建树,然后就知道每条边的权重,然后有限队列贪心即可,注意是所有叶子节点到根的距离和

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
using LL = long long;
class LinkStar {
public:
std::vector<int> head, nxt, to;
std::vector<LL> w;
LinkStar(int n) {
nxt.clear();
to.clear();
head = std::vector<int>(n + 1, -1);
}
void addedge(int u, int v, LL val) {
nxt.emplace_back(head[u]);
head[u] = to.size();
to.emplace_back(v);
w.emplace_back(val);
}
};
struct Node {
int d;
LL v;
Node(int _d, LL _v) : d(_d), v(_v) {}
bool operator<(const Node &A) const {
return (v + 1) / 2 * d < (A.v + 1) / 2 * A.d;
}
};
int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas;
std::cin >> cas;
while (cas--) {
int n;
LL s;
std::cin >> n >> s;
LinkStar diag(n);
for (int i = 1, u, v; i < n; ++i) {
LL w;
std::cin >> u >> v >> w;
diag.addedge(u, v, w);
diag.addedge(v, u, w);
}
std::priority_queue<Node> a;
bool vis[n + 1] = {};
std::function<int(int, LL)> dfs = [&](int u, LL val) -> int {
vis[u] = true;
int cnt = 0;
for (int i = diag.head[u]; ~i; i = diag.nxt[i]) {
int v = diag.to[i];
if (!vis[v]) cnt += dfs(v, diag.w[i]);
}
cnt = std::max(cnt, 1);
s -= val * cnt;
a.push({cnt, val});
return cnt;
};
dfs(1, 0);
int r = 0;
while (s < 0) {
auto [cnt, val] = a.top();
s += (val + 1) / 2 * cnt;
if(val > 1) a.push({cnt, val / 2});
a.pop();
++r;
}
print(r);
}
return 0;
}

1399E2: 同 E1,只是贪心的时候,枚举费用为 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
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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
using LL = long long;
class LinkStar {
public:
std::vector<int> head, nxt, to, c;
std::vector<LL> w;
LinkStar(int n) {
nxt.clear();
to.clear();
head = std::vector<int>(n + 1, -1);
}
void addedge(int u, int v, LL val, int cost) {
nxt.emplace_back(head[u]);
head[u] = to.size();
to.emplace_back(v);
w.emplace_back(val);
c.emplace_back(cost);
}
};
int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas;
std::cin >> cas;
while (cas--) {
int n;
LL s;
std::cin >> n >> s;
LinkStar diag(n);
for (int i = 1, u, v, c; i < n; ++i) {
LL w;
std::cin >> u >> v >> w >> c;
diag.addedge(u, v, w, c - 1);
diag.addedge(v, u, w, c - 1);
}
std::vector<std::tuple<int, int, LL>> a;
bool vis[n + 1] = {};
std::function<int(int, LL, int)> dfs = [&](int u, LL val, int cost) -> int {
vis[u] = true;
int cnt = 0;
for (int i = diag.head[u]; ~i; i = diag.nxt[i]) {
int v = diag.to[i];
if (!vis[v]) cnt += dfs(v, diag.w[i], diag.c[i]);
}
cnt = std::max(cnt, 1);
s -= val * cnt;
a.push_back({cost, cnt, val});
return cnt;
};
dfs(1, 0, 0);
std::vector<LL> q[2];
for (auto [cost, cnt, val] : a) {
while (val) {
q[cost].emplace_back((val + 1) / 2 * cnt);
val >>= 1;
}
}
std::sort(q[0].begin(), q[0].end(), std::greater<>());
std::sort(q[1].begin(), q[1].end(), std::greater<>());
int r = 1e9;
for (auto &x : q[0]) s += x;
for (int i = 0, j = q[0].size(); i <= q[1].size(); ++i) {
while (j > 0 && s - q[0][j - 1] >= 0) {
s -= q[0][--j];
}
if (s >= 0) r = std::min(r, 2 * i + j);
if (i != q[1].size()) s += q[1][i];
}
print(r);
}
return 0;
}

1399F : 区间 dp 问题

f[m] 表示从 a[i] 的左边界到,m 的区间个数(是被一个大的覆盖了的区间,size 一般不为 1)

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
// follow the idea of Jiangly
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
#define print(x) std::cout << (x) << std::endl
using LL = long long;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas;
std::cin >> cas;
while (cas--) {
int n;
std::cin >> n;
std::vector<std::pair<int, int>> a(n);
std::vector<int> v;
for (auto &[l, r] : a) std::cin >> l >> r;
a.push_back({1, 2e5});
for (auto &[l, r] : a) {
v.emplace_back(l);
v.emplace_back(r);
}
std::sort(v.begin(), v.end());
v.erase(std::unique(v.begin(), v.end()), v.end());
for (auto &[l, r] : a) {
l = std::lower_bound(v.begin(), v.end(), l) - v.begin();
r = std::lower_bound(v.begin(), v.end(), r) - v.begin();
}
std::sort(a.begin(), a.end(), [](const auto &lhs, const auto &rhs) {
if (lhs.first == rhs.first) return lhs.second < rhs.second;
return lhs.first > rhs.first;
});
int dp[n + 1] = {};
for (int i = 0; i <= n; ++i) {
std::vector<int> f(a[i].second + 1);
int mx = 0, x = a[i].first - 1;
for (int j = i - 1; j >= 0; --j) {
if (a[j].second > a[i].second) continue;
while (x + 1 < a[j].first) mx = std::max(mx, f[++x]);
f[a[j].second] = std::max(f[a[j].second], dp[j] + mx);
}
for (auto &t : f) mx = std::max(mx, t);
dp[i] = 1 + mx;
}
print(dp[n] - 1);
}
return 0;
}

1389F:二分图

emorgan5289 大佬的代码以及解释

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

multiset<array<int, 3>> a;
multiset<int> s;

int main() {
ios_base::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int n; cin >> n;
for (int i = 0; i < n; i++) {
int l, r, t; cin >> l >> r >> t;
a.insert(t-1 ? array{r, 1, -l} : array{l, 0, -r});
}
for (auto& [_, t, x] : a) {
if (t && s.upper_bound(x) != s.begin())
s.erase(--s.upper_bound(x)), n--;
if (!t) s.insert(x);
}
cout << n << "\n";
}

1384D:两个人轮流取 $n$ 个数,比较它们取的数的异或值,较大的赢

记 $s$ 为 $n$ 个数的异或值,如果 $s = 0$,那么显然平局,否则看与 $s$ 的最高位 异或不为 0 的数的人数 cnt,显然这个个数是奇数,所以为 $1 \mod 4$ ,那么先手赢,否则,我们看 $n - cnt$ 是否为奇数。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas;
std::cin >> cas;
while (cas--) {
int n;
std::cin >> n;
std::vector<int> a(n);
for (auto &x : a) std::cin >> x;
int sum = 0;
for (auto &x : a) sum ^= x;
if (sum == 0) {
std::cout << "DRAW\n";
} else {
int k = std::__lg(sum);
int cnt = 0;
for (auto &x : a) if((x >> k) & 1) ++cnt;
if (cnt % 4 == 1 || (n - cnt) % 2 == 1) {
std::cout << "WIN\n";
} else {
std::cout << "LOSE\n";
}
}
}
return 0;
}

1384B:很有意思的一道模拟题,我也不知道为什么我要想这么久,太菜了

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas;
std::cin >> cas;
while (cas--) {
int n, k, len;
std::cin >> n >> k >> len;
int d[n];
bool flag = false;
for (int i = 0; i < n; ++i) {
std::cin >> d[i];
if (d[i] > len) flag = true;
else d[i] = len - d[i];
}
if (flag) {
std::cout << "No\n";
continue;
}
int l = 0, r = k;
for (int i = 0; i < n; ++ i){
int li = std::min(k, d[i]);
int ri = std::max(k, 2 * k - d[i]);
if (r < 2 * k) {
l = 0;
r = std::max(r + 1, ri);
} else if (++l > li) flag = true;
l = std::min(l, k);
if (d[i] >= k) r = k;
}
std::cout << (flag ? "No" : "Yes") << std::endl;
}
return 0;
}

洛谷 U122053 选择题经典问题:每个人说另一个人是不是好人, 好人只说真话,坏人只说慌话,输出有多少种可能的情况,并输出可能的情况下,最多的好人和最少的好人数

val[i] 表示第 $i$ 个人是否为好人, w[i,j] 表示$i$ 说 $j$ 是好人还是坏人,或 $j$ 说 $i$ 是好人还是坏人,那么必然 val[i] ^ val[j] = !w[i,j]

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// https://www.luogu.com.cn/problem/U122053?contestId=31675
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
const LL M = 998244353;
const int N = 1e6 + 2;
int head[N], val[N], cnt, nxt[2 * N], to[2 * N];
bool w[2 * N];
void init() {
cnt = -1;
memset(head, -1, sizeof (head));
memset(val, -1, sizeof (val));
}
void addedge(int u, int v, bool flag) {
nxt[++cnt] = head[u];
head[u] = cnt;
to[cnt] = v;
w[cnt] = flag;
}

// 此题BFS更好,不过DFS也能过
std::pair<int, int> dfs(int u, int flag) {
val[u] = flag;
int r1 = 1, r2 = flag;
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i];
if (val[v] != -1) {
if (val[v] != (val[u] ^ w[i])) return {-1, 0};
} else {
auto [vr1, vr2] = dfs(v, val[u] ^ w[i]);
if (vr1 == -1) return {-1, 0};
r1 += vr1;
r2 += vr2;
}
}
return {r1, r2};
}

std::pair<int, int> bfs(int iu, int iflag) {
std::queue<std::pair<int, int>> q;
q.push({iu, iflag});
int r1 = 0, r2 = 0;
while (!q.empty()) {
auto [u, flag] = q.front();
q.pop();
if (val[u] != -1) continue;
val[u] = flag;
++r1;
r2 += flag;
for (int i = head[u]; ~i; i = nxt[i]) {
int v = to[i];
if (val[v] != -1) {
if (val[v] != (val[u] ^ w[i])) return {-1, 0};
} else q.push({v, val[u] ^ w[i]});
}
}
return {r1, r2};
}

LL powMod(int n) {
LL r = 1, x = 2;
while (n) {
if (n & 1) r = r * x % M;
n >>= 1; x = x * x % M;
}
return r;
}

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
init();
int n;
std::cin >> n;
for (int i = 1, x; i <= n; ++i) {
bool flag;
std::cin >> x >> flag;
addedge(i, x, !flag);
addedge(x, i, !flag);
}
int now = 0, mx = 0, mi = 0;
for (int i = 1; i <= n; ++i) if (val[i] == -1) {
// auto [r1, r2] = dfs(i, 1);
auto [r1, r2] = bfs(i, 1);
if (r1 == -1) {
std::cout << "No answer\n";
return 0;
}
++now;
mx += std::max(r2, r1 - r2);
mi += std::min(r2, r1 - r2);
}
std::cout << powMod(now) << std::endl;
std::cout << mx << std::endl;
std::cout << mi << std::endl;
return 0;
}

洛谷 U122055 出生点 :简单包容排斥

仅考虑 $x$-轴

当$k=0$ 时,那么距离之和就是

然后我们减去 $k$ 个障碍点和其它所有点之间的距离

再加上 $k$ 个障碍点之间的距离

不妨按照 $x$-轴排序,然后把前缀和 sa[i] 求一下,再依次计算

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;
const LL M = 1e9 + 7;
const LL inv2 = (M + 1)/2;
const LL inv6 = (M + 1)/6;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
LL n, m;
int k;
std::cin >> n >> m >> k;
LL a[k], b[k];
for (int i = 0; i < k; ++i) {
std::cin >> a[i] >> b[i];
}
std::sort(a, a + k);
std::sort(b, b + k);
LL sa[k] = {a[0]}, sb[k] = {b[0]};
for (int i = 1; i < k; ++ i) {
sa[i] = sa[i - 1] + a[i];
sb[i] = sb[i - 1] + b[i];
}

auto f = [](LL m, LL n) -> LL {
return m * m % M * (n - 1) % M * n % M * (n + 1) % M;
};
LL r0 = (f(m, n) + f(n, m)) * inv6 % M;

LL r1 = 0;
for (int i = 0; i < k; ++ i) {
r1 += ((a[i] - 1) * a[i] + (n - a[i] + 1) * (n - a[i])) % M;
}
r1 = r1 % M * m % M * inv2 % M;

LL r2 = 0;
for (int i = 0; i < k; ++ i) {
r2 += ((b[i] - 1) * b[i] + (m - b[i] + 1) * (m - b[i])) % M;
}
r2 = r2 % M * n % M * inv2 % M;

LL r3 = 0;
for (int i = 0; i < k; ++i) {
r3 += (sa[k - 1] - sa[i] - a[i] * (k - 1 - i)) % M;
r3 += (sb[k - 1] - sb[i] - b[i] * (k - 1 - i)) % M;
}
r3 %= M;

LL r = (r0 - r1 - r2 + r3) % M;
if(r < 0) r += M;
std::cout << r << std::endl;

return 0;
}

洛谷 U122054 强迫症 : 圆上 $n$ 个点构成的无交错边的图的个数 $f_n$

$f_0 = 1, f_1 = 1, f_2 = 2, f_3 = 8$,考虑 $n+1$ 个点,如果第 $n+1$ 点与其他点没有边相连有 $f_n$ 种,如果相连,设最小的数为 $i$, 那么一边有 $f_i$ 种(注意到 $n+1$ 这个节点可以不管,因为它不跟小于 $i$ 的节点相连),另一边有 $\frac{f_{n+2-i}}{2}$,即

化简一下,可知

我们令 $g_n = f_{n+1}$,则 $g_n = 2g_{n-1} + \sum_{i=2} ^ n g_{i-1} g_{n+1-i} = 2 g_{n-1} + \sum_{i=1} ^{n-1} g_i g_{n-i}$,所以 $g_n = \frac{2}{3} g_{n-1} + \frac{1}{3} \sum_{i=0} ^n g_i g_{n-i}$。考虑 $g_n$ 的生成函数 $g(z)$:

因此 $\frac{1}{3} g^2(z) + (\frac{2}{3} z - 1) g(z) + \frac{2}{3} = 0$,从而 $g(z) = \frac{3-2z \pm \sqrt{1 - 12 z + 4z^2}}{2}$, 由 $g_1 = 2$ 知 $g(z) = \frac{3-2z - \sqrt{1 - 12 z + 4z^2}}{2}$,从而 $f(z) = f_0 + z g(z) = 1 + \frac{3}{2} z - z^2 - \frac{z}{2} \sqrt{1 - 12 z + 4 z^2}$。

因此 $(n + 1)g_{n+1} - 12 n g_n + 4(n-1)g_{n-1} = 4 g_{n-1} - 6 g_n$,整理得到 $(n+1)g_{n+1} = (12 n - 6) g_n - (4n-8)g_{n-1}$

从而 $(n+1)f_{n+2} = (12 n - 6) f_{n+1} - (4n - 8) f_n$,即递推公式 $f_{n} = \frac{ (12 n - 30) f_{n-1} - (4 n - 16) f_{n-2} }{n-1}$。

所以原问题的答案就是

仅看分子:

如果我们定义 $b_i = a_{n-1-i}$,$c_t = \sum_{i = 0} ^{t} a_{t-i} b_{i} = \sum_{i = 0} ^{t} a_{i} b_{t-i} = \sum_{i = 0} ^{t} a_{i} a_{n-1-(t-i)}$,则 $c_{n-t} = \sum_{i = 0} ^{n-t} a_{i} a_{n-1-(n-t-i)} = \sum_{i = 0} ^{n-t} a_{i} a_{i+t-1}$ 。即答案为

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
83
84
85
// https://www.luogu.com.cn/problem/U122054?contestId=31675
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

const LL M = 998244353, ROOT = 3;
LL powMod(LL x, LL n) {
LL r(1);
while (n) {
if (n & 1) r = r * x % M;
n >>= 1;
x = x * x % M;
}
return r;
}
void bitreverse(std::vector<LL> &a) {
for (int i = 0, j = 0; i != a.size(); ++i) {
if (i > j) std::swap(a[i], a[j]);
for (int l = a.size() >> 1;
(j ^= l) < l; l >>= 1);
}
}
void nft(std::vector<LL> &a, bool isInverse = false) {
LL g = powMod(ROOT, (M - 1) / a.size());
if (isInverse) {
g = powMod(g, M - 2);
LL invLen = powMod(LL(a.size()), M - 2);
for (auto & x: a) x = x * invLen % M;
}
bitreverse(a);
std::vector<LL> w(a.size(), 1);
for (int i = 1; i != w.size(); ++i) w[i] = w[i - 1] * g % M;
auto addMod = [](LL x, LL y) {
return (x += y) >= M ? x -= M : x;
};
for (int step = 2, half = 1; half != a.size(); step <<= 1, half <<= 1) {
for (int i = 0, wstep = a.size() / step; i != a.size(); i += step) {
for (int j = i; j != i + half; ++j) {
LL t = (a[j + half] * w[wstep * (j - i)]) % M;
a[j + half] = addMod(a[j], M - t);
a[j] = addMod(a[j], t);
}
}
}
}
std::vector<LL> mul(std::vector<LL> a, std::vector<LL> b) {
int sz = 1, tot = a.size() + b.size() - 1;
while (sz < tot) sz *= 2;
a.resize(sz);
b.resize(sz);
nft(a);
nft(b);
for (int i = 0; i != sz; ++i) a[i] = a[i] * b[i] % M;
nft(a, 1);
a.resize(tot);
return a;
}

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
std::vector<LL> a(n);
for (auto &x : a) std::cin >> x;
std::vector<LL> b(a.rbegin(),a.rend());
auto c = mul(a, b);

LL inv[n] = {0, 1}, f[n + 1] = {1, 1};
for (int i = 2; i <= n; ++i) inv[i] = (M - M / i) * inv[M % i] % M;
for (int i = 2; i <= n; ++i) {
f[i] = ((12 * i - 30)* f[i - 1] - (4 * i - 16) * f[i - 2]) % M * inv[i - 1] % M;
if (f[i] < 0) f[i] += M;
}

LL r = 0;
for (int i = 2; i <= n; ++i) {
r += f[i] * f[n - i + 2] % M * c[n - i] % M;
}
r = r * inv[4] % M * powMod(f[n], M - 2) % M;
std::cout << r << std::endl;

return 0;
}

1382D :0-1 背包问题

注意到一段选择了 a[i] 那么后来连续小于 a[i] 的都要被选择。

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas;
std::cin >> cas;
while (cas--) {
int n, maxn;
std::cin >> n;
int a[2 * n];
for (int i = 0; i < 2 * n; ++i) {
std::cin >> a[i];
}
bool dp[n + 1] = {1};
for (int i = 0, j = 0; i < 2 * n; i = j) {
while (j < 2 * n && a[j] <= a[i]) ++j;
int len = j - i;
for (int k = n; k >= len; --k) {
dp[k] |= dp[k - len];
}
}
std::cout << (dp[n] ? "YES" : "NO") << std::endl;
}
return 0;
}

1382C :设 f: 0-1string ---> 0-1string将字符串 0 换成 1,1 换成 0,然后倒序。给定长度为 $n$ 的字符串$a, b$ 。每次可以改变 $a$ 的前缀,给出一种方案(不超过 $3n$,或者不超过 $2n$ )

  • 注意到取 $n$ 再取 $1$,再去 $n$,这样只会改变最后一个,所以就给出了不超过 $3n$ 的方案
  • 可以先将 $a$变成 全$0$,再把 $b$ 变成 全$0$ 的方案反序,然后再复合。变成 $0$ 的做法就是一直让前缀数字相同。

方案一:

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>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas;
std::cin >> cas;
while (cas--) {
int n;
std::string a, b;
std::cin >> n >> a >> b;
std::vector<int> q;
while (--n >= 0) {
if(a[n] != b[n]) {
q.emplace_back(n + 1);
q.emplace_back(1);
q.emplace_back(n + 1);
}
}
std::cout << q.size();
for (auto &x : q) std::cout << " " << x;
std::cout << std::endl;
}
return 0;
}

方案二:

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
#include <bits/stdc++.h>
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
using LL = long long;

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int cas;
std::cin >> cas;
while (cas--) {
int n;
std::string a, b;
std::cin >> n >> a >> b;
a += '0'; b += '0';
std::vector<int> qa, qb;
for (int i = 1; i <= n; ++i) {
if (a[i] != a[i-1]) qa.emplace_back(i);
if (b[i] != b[i-1]) qb.emplace_back(i);
}
qa.insert(qa.end(), qb.rbegin(), qb.rend());
std::cout << qa.size();
for (auto &x : qa) std::cout << " " << x;
std::cout << std::endl;
}
return 0;
}

百度之星 2020 初赛 1008:数论函数题

已知,$f(n) = \displaystyle \sum_{d|n} d \cdot [\gcd(d,\frac{n}{d}) == 1]$,求 $\displaystyle \sum_{n=1} ^N f(n)$

首先

其中 $g(n) = \displaystyle \sum_{d|n} d$,所以

其中 $h(n) = \sum_{i = 1} ^n g(i) = \sum_{d=1}^n d \lfloor \frac{n}{d} \rfloor$,求 $h(n)$ 有众所周知的 $O (\sqrt{n})$ 的算法,所以总时间复杂度为 $O(\sum \frac{\sqrt{N}}{l}) = O(\sqrt{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
#include <bits/stdc++.h>
using LL = long long;
#define watch(x) std::cout << (#x) << " is " << (x) << std::endl
const LL M = 1e9 + 7;
const int N = 1e6 + 2;

int mu[N];
void initmu() {
mu[1] = 1;
for (int i = 1; i < N; ++i) {
for (int j = i * 2; j < N; j += i) {
mu[j] -= mu[i];
}
}
}
int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false); std::cin.tie(nullptr);
initmu();
auto f = [](LL n) {
LL ret = 0;
for (LL l = 1, r; l <= n; l = r + 1) {
r = n / (n / l);
ret += ((n / l) % M) * ((l + r) % M) % M * ((r - l + 1) % M) % M;
}
return ret % M * ((M + 1) / 2) % M;
};
int cas;
std::cin >> cas;
while (cas--) {
LL n, ans = 0;
std::cin >> n;
for (LL i = 1; i * i <= n; ++i) {
if (mu[i] == 0) continue;
ans += (M + mu[i]) * i % M * f(n / (i * i)) % M;
}
std::cout << ans%M << std::endl;
}
return 0;
}

另外一种计算思考:考虑每一个 $d$ 对答案的贡献,则

Atcode:经典生成函数题

题目很容易转化成,满足所有$(a_1+\cdots+a_5)+2(b_1+\cdots+b_5) \leq N$的$a_1 \cdots a_5$之和,其中$a_i,b_i$ 均为非负整数

我一开始把 5 看作变量,从 1,2,3 一直推出 5 的公式,贼麻烦。后来 querty20002 给出了生成函数做法的 题解。答案唯一的依赖于 $N$,即可认为答案是关于 $N$ 的数列,那么它的生成函数即为:

所以答案就是 $\sum_{i=N\%2}^{11} \binom{11}{i} \binom{\frac{N-i-5}{2}+15}{15}$,所以我们选择 Python 交题 0.0

1
2
3
4
5
6
7
8
9
import math
M = 1000000007
T = int(input())
for i in range(T):
n = int(input())
r = 0
for i in range((n-5)%2,12,2):
r+=math.comb(11,i)%M*math.comb((n-i+25)//2,15)%M;
print(r%M)

或者 C++ 也行

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 namespace std;
using LL = long long;
constexpr LL M = 1e9+7;
LL inv(LL a,LL p){
return a==1?1:(p-p/a)*inv(p%a,p)%p;
}
LL C(LL n, int k){
if(n<k) return 0;
if(k==0) return 1;
LL r = 1;
for(int i=0;i<k;++i){
r = r*(n-i)%M*inv(i+1,M)%M;
}
return r;
}
int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int cas;
cin >> cas;
while(cas--){
LL n ,r = 0;
cin >> n;
for(int i=(n+1)%2;i<=11;i+=2) {
r+=C(11,i)*C((n-i+25)/2,15)%M;
}
cout<<r%M<<endl;
}
return 0;
}

1215E:给定长度为 $n$ 数据范围 $[1,20]$ 的数组,求最少交换相邻位置元素使得相同的数字都紧挨着

参考 cf1215e,注意到交换两个数字,其它数字的相对位置不发生改变

状态 dp:设 dp[mask] 表示状态为 mask 的最小交换次数,其中 mask&(1<<i)=1 表示值为 1+i 的数已经被考虑到了。所以 $dp[2^{20}-1]$ 就是我们要的结果。状态转移

其中 cnt(j,k) 表示把 所有的 j 移动到 k 前面需要的次数。

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
#include<bits/stdc++.h>
using namespace std;
using LL = long long;
#define watch(x) cout << (#x) << " is " << (x) << endl
constexpr int N = 20;
LL cnt[N][N],s[N],dp[1<<N];

int main(){
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int n;
cin>>n;
for(int i=0,x;i<n;++i){
cin>>x;--x;
for(int j=0;j<N;++j) if(x!=j){
cnt[x][j] += s[j];
}
++s[x];
}
memset(dp,0x3f,sizeof(dp));
dp[0]=0;
for(int i=0;i!=(1<<N);++i){
for(int j=0;j<N;++j) if(!(i&(1<<j))){
LL s = dp[i];
for(int k=0;k<N;++k) if(i&(1<<k)){
s+=cnt[j][k];
}
dp[i|(1<<j)] = min(dp[i|(1<<j)],s);
}
}
cout<<dp[(1<<N)-1]<<endl;
return 0;
}

以上代码基本 copy WZYYN代码

1228E:(二维)包容排斥原理

在 $n \times n$ 的格子中填不超过$k$的正整数,使得每行每列的最小值都为 1 的填法方案数。下图是 官方题解

1228E

无论是 dp 还是包容排斥,定义好状态是最重要的。

1148B:在一次转机的旅行中,取消 $k$ 个航班,使得旅客最晚达到终点

一开始被卡住了,想一下子吃个胖子 0.0 其实枚举起点取消的航班数,然后取最大值即可

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
#include<bits/stdc++.h>
using namespace std;
using LL = long long;

int main(){
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int n,m,ta,tb,k;
cin>>n>>m>>ta>>tb>>k;
int a[n],b[m];
for(int i=0;i<n;++i){
cin>>a[i];
a[i]+=ta;
}
for(int i=0;i<m;++i) cin>>b[i];
if(n<=k||m<=k){
cout<<-1<<endl;
}else{
int ans = 0;
for(int i=0;i<=k;++i){
int id = lower_bound(b,b+m,a[i])-b;
if(id+k-i>=m){
ans = -1;break;
}
ans = max(ans,tb+b[id+k-i]);
}
cout<<ans<<endl;
}
return 0;
}

1251F: 组成周长固定的先单调递增后单调递减的序列

很快就能想到周长为 $2(L+x)$,其中 $L,x$ 分别表示序列最大值和长度。一开始想不明白的地方在于,如果有两个长度相同的怎么计数,后来发现相同的可以标记一个只能放左边,另一个只能放右边。

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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include<bits/stdc++.h>
using namespace std;
using LL = long long;
constexpr LL M = 998244353,ROOT=3;
LL powmod(LL x,LL n){
LL r(1);
while(n){
if(n&1) r=r*x%M;
n>>=1; x=x*x%M;
}
return r;
}
void bitreverse(vector<LL> &a){
for(int i=0,j=0;i!=a.size();++i){
if(i>j) swap(a[i],a[j]);
for(int l=a.size()>>1;(j^=l)<l;l>>=1);
}
}
void nft(vector<LL> &a,bool isInverse=false){
LL g = powmod(ROOT,(M-1)/a.size());
if(isInverse){
g = powmod(g,M-2);
LL invLen = powmod(LL(a.size()),M-2);
for(auto &x:a) x=x*invLen%M;
}
bitreverse(a);
vector<LL> w(a.size(),1);
for(int i=1;i!=w.size();++i) w[i] = w[i-1]*g%M;
auto addMod = [](LL x,LL y){return (x+=y)>=M?x-=M:x;};
for(int step=2,half = 1;half!=a.size();step<<=1,half<<=1){
for(int i=0;i!=a.size();i+=step){
for(int j=0;j!=half;++j){
LL t = (a[i+j+half]*w[a.size()/step*j])%M;
a[i+j+half]=addMod(a[i+j],M-t);
a[i+j]=addMod(a[i+j],t);
}
}
}
}
vector<LL> mul(vector<LL> a,vector<LL> b){
int sz=1,tot = a.size()+b.size()-1;
while(sz<tot) sz*=2;
a.resize(sz);b.resize(sz);
nft(a);nft(b);
for(int i=0;i!=sz;++i) a[i] = a[i]*b[i]%M;
nft(a,1);
a.resize(tot);
return a;
}
constexpr int N = 3e5+2;
LL fac[N],ifac[N];
void init(){
fac[0]=1;
for(int i=1;i<N;++i) fac[i] = fac[i-1]*i%M;
ifac[N-1] = powmod(fac[N-1],M-2);
for(int i=N-1;i;--i) ifac[i-1] = ifac[i]*i%M;
}
LL binom(int n,int k){
return fac[n]*ifac[k]%M*ifac[n-k]%M;
}

int main(){
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
init();
int n,k,x,q;
cin>>n>>k;
int cnt[N] = {};
for(int i=0;i<n;++i){
cin>>x;
++cnt[x];
}
vector<LL> ans(2*N);
while(k--){
cin>>x;
int c1=0,c2=0;
for(int i=1;i<x;++i){
if(cnt[i]>1) c2+=2;
else if(cnt[i]==1) ++c1;
}
vector<LL> a(c1+1),b(c2+1);
auto inc = [](LL &a,LL b){if((a+=b)>=M) a-=M;};
LL p2=1;
for(int i=0;i<=c1;++i){
a[i] = binom(c1,i)*p2%M;
inc(p2,p2);
}
for(int i=0;i<=c2;++i) b[i] = binom(c2,i);
a = mul(a,b);
for(int i=0;i!=a.size();++i){
inc(ans[i+x+1],a[i]);
}
}
cin>>q;
while(q--){
cin>>x;
cout<<ans[x/2]<<endl;
}
return 0;
}

1251E: 花最少的钱,让所有人都投票给自己(选民跟风且贪财)

我们按照 $m$ 分层,然后从大到小记录白嫖的人,然后实在没法白嫖的,就取消费最少的。

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 namespace std;
using LL = long long;

int main(){
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int cas;
cin>>cas;
while(cas--){
int n;
cin>>n;
vector<int> a[n];
for(int i=0,x,y;i<n;++i){
cin>>x>>y;
a[x].emplace_back(y);
}
LL r = 0;
// q 也可以用 multiset 取代,会稍微快点
priority_queue<int,vector<int>,greater<int>> q; //白嫖的votes
for(int i=n-1;i>=0;--i){
for(auto &x:a[i]) q.push(x);
while(n-q.size()<i){
r+=q.top();
q.pop();
}
}
cout<<r<<endl;
}
return 0;
}

1375D: MEX once more,通过修改数组的某一个值成 mex,使得数组最终非降

不妨最终变成 0~n-1,这是不好想的,特别是紧张的比赛的时候

如果当前mex = n 即数列正好是一个排列,此时选择任意一个a[i]!=i 的位置,让 a[i]=n,否则 mex < n 此时令a[mex]=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
48
#include<bits/stdc++.h>
using namespace std;
using LL = long long;

int main(){
//freopen("in","r",stdin)
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int cas;
cin>>cas;
while(cas--){
int n;
cin>>n;
int a[n],v[n],c[n+1]={},step=n;
for(int i=0;i<n;++i){
cin>>a[i];
++c[a[i]];
v[i] = (a[i]==i);
if(v[i]) --step;
}
vector<int> q;
int mex = 0,l=0;
while(c[mex]) ++mex;
while(step--){
if(mex == n){
while(v[l]) ++l;
q.push_back(l+1);
--c[a[l]];
++c[mex];
mex = a[l];
a[l] = n;
}
++c[mex];
int nmex = mex;
if(--c[a[mex]]==0&&a[mex]<mex){
nmex = a[mex];
}
a[mex] = mex;
v[mex] = true;
q.push_back(mex+1);
mex = nmex;
while(c[mex]) ++mex;
}
cout<<q.size()<<endl;
for(auto &x:q) cout<<x<<" ";
cout<<endl;
}
return 0;
}

上面算法过度追求效率而丢失了可读性。其实可以缩短一半代码量

1119C: 选取 4 个角,反位,使得 A 矩阵变成 B 矩阵

把 A,B 异或到 A,然后就转化成 A 矩阵变成 0 矩阵的方案了。这个时候一行行的处理就搞定了!(一开始就是没思路,后来灵光一闪)

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
#include<bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 502;
int a[N][N];
bool f(int n,int m){
for(int i=1;i<=n;++i){
int cnt = 0;
for(int j=1;j<=m;++j){
if(a[i][j]){
++cnt;
if(i!=n){
a[i+1][j]^=a[i][j];
}
}
}
if(cnt&1) return false;
if(i==n) return cnt==0;
}
}
int main(){
//freopen("in","r",stdin)
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int n,m;
cin>>n>>m;
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
cin>>a[i][j];
}
}
for(int i=1;i<=n;++i){
for(int j=1,x;j<=m;++j){
cin>>x;
a[i][j]^=x;
}
}
cout<<(f(n,m)?"Yes":"No")<<endl;
return 0;
}

1110:打麻将 0.0

就是计算最容易听牌的数量,ABC 和 AAA 的个数和

考虑 ans[n][i][j] 表示只考虑小于 n 的情况下,有 i(n-1,n)jn 剩余的答案。由于三个 (n,n+1,n+2) 可以转化成 (n,n,n),(n+1,n+1,n+1),(n+2,n+2,n+2),所以 i,j 都小于 3。且 ans[n+1][j][k] = max(ans[n][i][j]+i+(c[n+1]-i-j-k)/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
26
27
28
29
30
31
32
33
#include<bits/stdc++.h>
using namespace std;
using LL = long long;

int main(){
//freopen("in","r",stdin)
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int cnt,m;
cin>>cnt>>m;
int c[m+1]={};
for(int i=0,x;i<cnt;++i){
cin>>x;
++c[x];
}
int dp[3][3],new_dp[3][3];
memset(dp,-1,sizeof(dp));
dp[0][0] = 0;
for(int n=0;n<m;++n){
memset(new_dp,-1,sizeof(new_dp));
for(int i=0;i<3;++i){
for(int j=0;j<3;++j){
for(int k=0;k<3;++k){
if(i+j+k<=c[n+1]&&dp[i][j]>=0){
new_dp[j][k] = max(new_dp[j][k],dp[i][j]+i+(c[n+1]-i-j-k)/3);
}
}
}
}
swap(new_dp,dp);
}
cout<<dp[0][0]<<endl;
return 0;
}

1371E:给定长为 $n$ 的数组和素数 $p$,记满足 $x+i < a_{\sigma(i)},0\leq i<n$ 的排列个数为 $f(x)$,输出所有 $x$ 使得 $p \not | n$

先排序,并且注意到 $x \in (\max a_i-n,\max a_i+n)$ 之间。

记 $b_i$ 为数组中小于等于 $i$ 的元素个数。则 $f(x) = \prod\limits_{i=x}^{x+n-1} b_i-(i-x) = \prod \limits_{i=x}^{x+n-1} x-(i-b_i)$,所以我们预处理出 $i-b_i$ 即可。

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
#include<bits/stdc++.h>
using namespace std;
using LL = long long;

int main(){
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int n,p;
cin>>n>>p;
int a[n];
for(int i=0;i<n;++i) cin>>a[i];
int mx = *max_element(a,a+n);
int bb[n*2+2]={};
int lx = mx-n;
int *b = bb - lx; // 黑科技,哈哈
for(int i=0;i<n;++i) ++b[max(lx,a[i])];
for(int i=1;i<n*2+2;++i) bb[i]+=bb[i-1];
int mp[n]={};
auto modp = [](int x,int p){
x%=p;
return x<0?x+p:x;
};
for(int i=lx;i<=mx;++i){
++mp[modp(i-b[i],p)];
}
vector<int> q;
for(int i=lx;i<=mx;++i){
if(mp[modp(i,p)]==0){
q.emplace_back(i);
}
--mp[modp(i-b[i],p)];
++mp[modp(i+n-b[i+n],p)];
}
cout<<q.size()<<endl;
for(int i=0;i!=q.size();++i){
cout<<q[i]<<" \n"[i == q.size()-1];
}
return 0;
}

1245F:经典 XOR 递归

在区间 [l,r) 中找 (a,b) 使得 $a+b = a \wedge b$ 的个数 $f(l,r)$。

显然,$f(x,x) = 0 \; (x\neq 0),\; f(2l,2r) = 3f(l,r)$,若 $l$ 为奇数,那么 $f(l,r) = f(l+1,r) + 2(g(l,r)-g(l,l))-(l==0)$,若 $r$ 为奇数,那么 $f(l,r) = f(l,r-1) + 2(g(r-1,r)-g(r-1,l))-(r==1)$ 。其中 $g(x,n)$ 表示满足下式的 $y$ 的个数:

通过比较 $x,n$ 的二进制,$g(x,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
#include<bits/stdc++.h>
using namespace std;
using LL = long long;

int main(){
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
function<int(int,int)> g = [&](int x ,int n)->int{
int ret = 1,zeros=0;
for(int i=1;i<=n;i<<=1){
if(n&i){
n^=i;
if(!(x&n)) ret += 1<<zeros;
}
if(!(x&i)) ++zeros;
}
return ret;
};
function<LL(int,int)> f = [&](int l ,int r)->LL{
if(l==r) return 0;
LL ret = 0;
if(l&1){
ret += (g(l,r)-g(l,l))*2-(l==0);
++l;
}
if(r&1){
ret += (g(r-1,r)-g(r-1,l))*2-(r==1);
--r;
}
return ret + 3*f(l/2,r/2);
};
int cas;
cin>>cas;
while(cas--){
int l,r;
cin>>l>>r;
cout<<f(l,r+1)<<endl;
}
return 0;
}

1373F:$f(x)$ 表示非负整数 $x$ 的十进制表示的各位数之和。求最小的非负整数 $x$ 使得 $\sum_{i=0} ^k f(x+i) = n$

由于 $0 \leq k \leq 9$,也就是说 $x,x+1,\cdots,x+k$,的个位数各不相同。我们可以枚举 $x$ 的个位数,那么 $x/10,\cdots, (x+k)/10$,最多仅有两种取值。若只有一种,即没有进位(不能为 9),那么直接就可以把 $x$ 的位数和求出来,否则 $x/10$ 的个数必为 9。

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
#include<bits/stdc++.h>
using namespace std;
using LL = long long;
const LL inf = 1e17+2;
int main(){
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
auto csum = [](int a,int b){
return (a+b)*(b-a+1)/2;
};
auto f = [](int n){
LL r = 0,d = 1;
while(n>9){
r+=9*d;
d*=10;
n-=9;
}
return r+n*d;
};
auto g = [&](int a,int k,int n) -> LL{
LL r = 0,d = 1;
if(n<a) return inf;
while((n-a)%k){
n-=9*(k-a);
r+=d*9;
d*=10;
if(n<a) return inf;
}
return r+d*(f((n-a)/k+1)-1);
};
int cas;
cin>>cas;
while(cas--){
int n,k;
cin>>n>>k;
if(k==0){
cout<<f(n)<<endl;
continue;
}
if(k*(k-1)/2 > n){
cout<<-1<<endl;
continue;
}
LL r = inf;
for(int i=0;i<=9;++i){
if(i+k<=9){
int t = csum(i,i+k);
if(n>=t&&(n-t)%(k+1)==0) r = min(r,10*f((n-t)/(k+1))+i);
}else{
int t = csum(i,9)+csum(0,i+k-10);
if(n>=t) r = min(r,10*g(i+k-9,k+1,n-t)+i);
}
}
cout<<(r==inf? -1:r)<<endl;
}
return 0;
}

以上代码按照时间顺序(2020/6/25——now)倒序,以下代码按照时间顺序(2020/5/22 —— 2020/6/25)排序

1355B :贪心模仿加法进位

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
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
int a[N];
int getans(int n){
int ans = 0,x=0;
for(int i=1;i<=n;++i){
x+=a[i];
ans+=x/i;
x%=i;
}
return ans;
}

int main(){
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int cas,n,x;
cin>>cas;
while(cas--){
cin>>n;
for(int i=1;i<=n;++i) a[i]=0;
for(int i=0;i<n;++i){
cin>>x;
++a[x];
}
cout<<getans(n)<<endl;
}
return 0;
}

1354C :计算包含单位正 $n$ 边形的最小正方形的边长

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <bits/stdc++.h>
using namespace std;
const double pi = acos(-1.0);

int main(){
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int cas,n;
cin>>cas;
cout.precision(10);
while(cas--){
cin>>n;
if(n&1){
double x = sqrt(1-cos((n/2)*pi/n));
double y = sqrt(1-cos((n/2+1)*pi/n));
cout<<(x+y)/2/sin(pi/2/n)<<endl;
}else cout<<1.0/tan(pi/2/n)<<endl;
}
return 0;
}

1354D :模拟操作: 加数字或删除第 $k$ 小的数

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
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
int s[N], Size;

int lowbit(int n) {
return n & (-n);
}
void add(int id, int p) {
while (id <= Size) {
s[id] += p;
id += lowbit(id);
}
}
int sum(int id) {
int r = 0;
while (id) {
r += s[id];
id -= lowbit(id);
}
return r;
}
int find(int k) {
int l = 0, r = Size;
while (r>l) {
int m = (l + r) >> 1;
if (sum(m) >= k) r = m;
else l = m+1;
}
return r;
}
void del(int k) {
add(find(k), -1);
}

int main() {
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int q, x;
while (cin >> Size >> q) {
for (int i = 1; i <= Size; ++i) s[i] = 0;
for (int i = 1; i <= Size; ++i) {
cin >> x;
add(x, 1);
}
while (q--) {
cin >> x;
if (x > 0) add(x, 1);
else {
del(-x);
}
}
int ans = find(1);
cout <<(sum(ans)>0?ans:0)<< endl;
}
return 0;
}

1354F :经典 DP 问题

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
#include<bits/stdc++.h>
using namespace std;
const int N = 102;
int dp[N][N];
bool isin[N][N],chose[N];
using node = tuple<int,int,int>;
node q[N];
void getans(int n,int k){
for(int i=1;i<=n;++i){
dp[i][0] = dp[i-1][0]+(k-1)*get<2>(q[i]);
for(int j = 1;j<i && j<=k;++j){
int x = dp[i-1][j]+(k-1)*get<2>(q[i]);
int y = dp[i-1][j-1]+(j-1)*get<2>(q[i])+get<1>(q[i]);
if(x>y){
dp[i][j] = x;
isin[i][j] = false;
}else{
dp[i][j] = y;
isin[i][j] = true;
}
}
if(i<=k){
dp[i][i] = dp[i-1][i-1]+(i-1)*get<2>(q[i])+get<1>(q[i]);
isin[i][i] = true;
}
}
for(int i=n,j=k;i;--i){
if(isin[i][j]){
chose[i]=true;
--j;
}else{
chose[i]=false;
}
}
}

int main(){
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int cas,n,k,a,b;
cin>>cas;
while(cas--){
cin>>n>>k;
for(int i=1;i<=n;++i){
cin>>a>>b;
q[i] = {i,a,b};

}
sort(q+1,q+n+1,[](const node & x, const node & y){
return get<2>(x)<get<2>(y);
});
cout<<(2*n-k)<<endl;
getans(n,k);
int last = 0;
for(int i=1;i<=n;++i){
if(chose[i]){
if(++last == k){
last = i;
break;
}
cout<<get<0>(q[i])<<" ";
}
}
for(int i=1;i<=n;++i){
if(!chose[i]) cout<<get<0>(q[i])<<" "<<-get<0>(q[i])<<" ";
}
cout<<get<0>(q[last])<<endl;
}
return 0;
}

1345F :经典二分,利用二阶偏导(离散偏导,即增量)是常量

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
#include<bits/stdc++.h>
using namespace std;
using LL = long long;

int main(){
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int n;
LL k,x;
while(cin>>n>>k){
vector<LL> q;
for(int i=0;i<n;++i){
cin>>x;
q.push_back(x);
}
auto get = [](LL x ,LL mx){
LL l=0,r=x-1;
while(l<=r){
LL m=(l+r)>>1;
if(x-m*(m+1)*3 == mx) return make_pair(m,m+1);
if(x-m*(m+1)*3 > mx) l = m+1;
else r = m-1;
}
return make_pair(l,l);
};
LL l = -3e18-3e8, r = 1e9;
while(l<=r){
LL m = (l+r)>>1,x=0,y=0;
for(auto &i:q){
auto tmp = get(i,m);
x+=tmp.first;
y+=tmp.second;
}
if(x>k) l = m+1;
else if(y<k) r = m-1;
else{
y-=k;
for(auto &i:q){
auto tmp = get(i,m);
//cout<<endl<<tmp.first<<" "<<tmp.second<<endl;;
if(tmp.first<tmp.second && y-->0){
cout<<tmp.first<<" ";
}else{
cout<<tmp.second<<" ";
}
}
cout<<endl;
break;
}
}
}
return 0;
}

1325B:Set 的用法举例

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

int main(){
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int cas,n,x;
cin>>cas;
while(cas--){
cin>>n;
set<int> q;
for(int i=0;i<n;++i){
cin>>x;
q.insert(x);
}
cout<<q.size()<<endl;
}
return 0;
}

1325D:(异或)位运算:判断是否有 $n$ 个数,异或和为 $u$, 和为 $v$

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

int main(){
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
LL u,v;
while(cin>>u>>v){
if(u>v||((v-u)&1)){
cout<<-1<<endl;
}else if(u == v){
if(u) cout<<1<<endl;
cout<<u<<endl;
}else{
v = (v-u)>>1;
if(u&v) cout<<3<<endl<<u<<" "<<v<<" "<<v<<endl;
else cout<<2<<endl<<u+v<<" "<<v<<endl;
}
}
return 0;
}

注意到 $a+b = a \wedge b + 2(a \& b)$,并且 $(a \wedge b) \& (a\&b) = 0$ 当且仅当 $a b = 0$

1358D:给定区间最值问题,但是可以写的很漂亮

注意到区间最值一定在左或者右端点达到局部最大值(极值)

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
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 4e5+102;
int n,n2;
LL a[N],b[N],s[N];
LL f(LL x){
return ((x+1)*x)>>1;
}
LL sumx(LL x){
int it = lower_bound(b,b+n2,x) - b;
// 这里注意是it, 比赛的时候没减,搞得怀疑人生
return s[it-1]+f(x-b[it-1]);
}
int main(){
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
LL d;
cin>>n>>d;
n2=n<<1;
for(int i=0;i<n;++i){
cin>>a[i];
a[i+n] = a[i];
}
b[0] = a[0];s[0] = f(a[0]);
for(int i=1;i<n2;++i){
b[i]=b[i-1]+a[i];
s[i]=s[i-1]+f(a[i]);
}
LL ans = 0;
for(int i=0;i<n;++i){
ans = max(ans,sumx(b[i]+d-1)-s[i]+a[i]);
}
for(int i=n;i<n2;++i){
ans = max(ans,s[i]-sumx(b[i]-d));
}
cout<<ans<<endl;
return 0;
}

1358E:很有意思的数据处理题目,官方题解

注意到如果 $k$ 满足答案,则 $2k$ 也满足,所以不妨设 $k> \lfloor \frac{n}{2} \rfloor$,另一方面题目中要求若 $i> \lceil \frac{n}{2} \rceil$,则 $a_{i} = x$

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
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 5e5+5;
LL a[N],s[N],m[N];
int main(){
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int n;
cin>>n;
int n2 = (n+1)>>1;
for(int i=1;i<=n2;++i){
cin>>a[i];
s[i] = s[i-1]+a[i];
}
LL x;
cin>>x;
for(int i=n2+1;i<=n;++i){
a[i]=x;
s[i]=s[i-1]+x;
}
for(int i=1;i<=n2;++i){
m[i+1] = min(m[i],x*i-s[i]);
}
for(int k=n2;k<=n;++k){
if(s[k]+m[n-k+1]>0){
cout<<k<<endl;
return 0;
}
}
cout<<-1<<endl;
return 0;
}

1359C: 读题被坑…(反正就是倒冷热水接近给定温度)

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
#include <bits/stdc++.h>
using namespace std;
using LL = long long;

int main(){
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int cas;
cin>>cas;
while(cas--){
int h,t,c;
cin>>h>>c>>t;
h-=c;t-=c;
if(h>=2*t){
cout<<2<<endl;
}else{
LL n = (h-t)/(2*t-h);
auto f = [](LL n,int h,int t){
return (n+1)*(2*n+3)*h+(2*n+1)*(n+2)*h>(2*n+1)*(2*n+3)*t*2;
};
if(f(n,h,t)) ++n;
cout<<2*n+1<<endl;
}
}
return 0;
}

然后对于奇数项,单调递减趋于平均问题,然后判断的时候转化成整数的判断

1359D:线段树,但是由于数据特殊,可以不用线段树

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
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 5e5+5;
int a[N];
int main(){
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(NULL);
int n;
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i];
}
int ans=0;
for(int m=1;m<=30;++m){
int suml =0,sum=0,maxa = -31;
for(int i=1;i<=n;++i){
if(a[i]>m){
sum = suml = 0;
maxa = -31;
}else{
sum+=a[i];
//这里取全局最大是因为,我们枚举了最大值的可能
maxa = max(maxa,a[i]);
ans = max(ans,sum-suml-maxa);
//相当于前面是全0的部分删了!并且这样不会避免中间负的情况
suml = min(sum,suml);
}
}
}
cout<<ans<<endl;
return 0;
}

注意到 $-30<a_i<30 $

1359E:找到公式后就是模素数组合数

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
#include <bits/stdc++.h>
using namespace std;
using LL=long long;
const int N = 5e5+2;
const LL M = 998244353;
LL inv[N],frac[N];
LL powmod(LL x, LL n, LL p) {
LL r=1;
while(n) {
if(n&1) r=r*x%p;
n>>=1;
x=x*x%p;
}
return r;
}
void init(){
frac[0]=inv[0]=1;
for(int i=1;i<N;++i){
frac[i] = frac[i-1]*i%M;
}
inv[N-1] = powmod(frac[N-1],M-2,M);
for(int i=N-2;i;--i){
inv[i] = inv[i+1]*(i+1)%M;
}
}
LL C(int n,int m){
if(n<m) return 0;
if(n==m||m==0) return 1;
return frac[n]*inv[m]%M*inv[n-m]%M;
}

int main() {
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(NULL);
int n,k;
cin>>n>>k;
init();
LL ans = 0;
for(int i=1;i<=n;++i){
int x = n/i;
if(x<k) break;
ans += C(x-1,k-1);
}
cout<<ans%M<<endl;
return 0;
}

先考虑$k=2$ 的情况知道 $a_1|a_2$,然后发现对于任意$k$,仅需 $a_1|a_i$ 即可。

1363E:有根数,互换子树的节点位数到预定值,dfs 和图存储范例

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 namespace std;
using LL = long long;
// head[u] 和 cnt 的初始值都为 -1
const int N = 2e5+2;
int a[N];
vector<int> g[N];
bool b[N],c[N];

tuple<int,int,LL> dfs(int u,int parent,int mn){
int l=0,r=0;
LL cost=0;
if(b[u]!=c[u]){
if(b[u]) ++l;
else ++r;
}
for(auto &v:g[u]){
if(v == parent) continue;
auto [sl,sr,scost] = dfs(v,u,min(mn,a[u]));
l+=sl;r+=sr;cost+=scost;
}
if(a[u]<mn){
int take = min(l,r);
cost += LL(a[u])*(take*2);
l -= take;
r -= take;
}
return {l,r,cost};
}

int main(){
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int n,u,v;
cin>>n;
for(int i=1;i<=n;++i){
cin>>a[i]>>b[i]>>c[i];
}
for(int i=1;i<n;++i){
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
auto [l,r,cost] = dfs(1,0,2e9);
cout<<(l||r?-1:cost)<<endl;
return 0;
}

上面代码写得呢,就很优雅,哈哈!

1361E :下面代码至今没过我也是没懂为什么

模仿进制的操作,最后用其他代码过的题。多设几个变量没坏处的其实。

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
#include<bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 1e6+2;
const LL M = 1e9+7;
LL a[N],p;
template<typename T,typename U>
T powmod(T x,U n,T p){
T r(1);
while(n){
if(n&1) r=r*x%p;
n>>=1; x=x*x%p;
}
return r;
}
int main(){
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int cas,n;
cin>>cas;
while(cas--){
cin>>n>>p;
for(int i=1;i<=n;++i){
cin>>a[i];
}
if(p==1){
cout<<(n&1)<<endl;continue;
}
sort(a+1,a+n+1);
LL ans = 1;
while(n>1){
if(ans==0){
ans = 1;
}else{
if(a[n]==a[n-1]) --ans;
else{
while(a[n]>a[n-1]){
ans*=p;
--a[n];
if(ans>n) break;
}
if(a[n]!=a[n-1]) break;
--ans;
}
}
--n;
}
ans = ans%M*powmod(p,a[n],M)%M;
for(int i=1;i<n;++i){
ans-=powmod(p,a[i],M);
}
ans = (M+ans%M)%M;
cout<<ans<<endl;
}
return 0;
}

1349F :神奇的对应

我们称一个长度为 $n$ 的序列 $p$ 为好序列,如果对任意正整数$k>1$,存在 $1 \leq i < j \leq n$ 使得,$p_i = k-1, p_j = k$

存在 好序列 和长为 $n$ 的排列一一对应:

  • 给定排列 $a_1,a_2,\cdots, a_n$ , 相邻两个之间添加 >< ,那么 $p_{a_i}$ 就定义为 $a_1,\cdots,a_i$ 中 < 个数加一
  • 给定好序列 $p$,从右到左依次标记出 1,2,... ,直到标记完所有数即得到了排列

因此 好序列 中最大值对应这排列中单调递减区间的个数!

答案要求的是:所有好序列中出现 $k$ 的个数之和。

代码下次再写吧…

1365E:这题其实没啥,但是比赛的时候竟然把 | 想成了 ^ ,很烦

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 namespace std;
using LL = long long;
int main(){
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int n;
while(cin>>n){
LL a[n];
for(int i=0;i<n;++i){
cin>>a[i];
}
sort(a,a+n,greater<LL>());
LL ans = a[0];
for(int i=0;i<n;++i){
if(2*a[i]<=a[0]) break;
for(int j=i+1;j<n;++j){
LL t = a[i]|a[j];
ans = max(ans,t);
for(int k=j+1;k<n;++k){
ans = max(ans,t|a[k]);
}
}
}
cout<<ans<<endl;
}
return 0;
}

1312D:计算满足条件的数列个数

计算满足下列条件的数列个数:

  • 数列项数为 $n$,且每一项都是不超过 $m$ 的正整数,$2\cdot 10^5 =N>m \geq n \geq 2$
  • 数列中有且仅有两项是相同的
  • 数列在 $i$ 项前严格单调递增,$i$ 项后严格单调递减

我们枚举 $i$ 的位置和 $i$ 的值,以及相同的项,则显然有如下计算式

预处理一下阶乘和阶乘逆,我们就可以在 $O(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
#include<bits/stdc++.h>
using namespace std;
using LL = long long;
const LL M = 998244353;
const int N = 2e5+2;
LL powmod(LL a,LL n){
LL r(1);
while(n){
if(n&1) r=r*a%M;
n>>=1; a=a*a%M;
}
return r;
}
LL fac[N],ifac[N];
void init(){
fac[0]=1;
for(int i=1;i!=N;++i){
fac[i] = fac[i-1]*i%M;
}
ifac[N-1] = powmod(fac[N-1],M-2);
for(int i=N-2;~i;--i){
ifac[i] = ifac[i+1]*(i+1)%M;
}
}
int main(){
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
init();
int n,m;
while(cin>>n>>m){
LL x=0,y=0;
for(int i=n-1;i<=m;++i){
x+=fac[i-1]*ifac[i-n+1]%M;
}
for(int i=2;i<n;++i){
y+=ifac[i-2]*ifac[n-i-1]%M;
}
cout<<(x%M)*(y%M)%M<<endl;
}
return 0;
}

但是官方题解给了一个更简单的方法

首先把 $n=2$ 时无解,所以考虑 $n>2$ 的情况,首先把所有用到的数字选好:$m \choose n-1$,然后选择好相同的数字:$n-2$,然后剩下的非最大的数,要么放在最大的数左边要么放在最大的数右边:$2^{n-3}$,即最终答案是 ${m \choose n-1}(n-2)2^{n-3}$

1312E:经典 DP,解释放在 动态规划

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
#include<bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 1022;
const int inf = 0x3f3f3f3f;
int ans[502][N],b[502][N];

int main(){
//freopen("in","r",stdin);
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int n,x;
while(cin>>n){
memset(ans,inf,sizeof(ans));
cin>>x;
ans[0][x] = 1;
b[0][x]=0;
for(int i=1;i<n;++i){
cin>>x;
for(int j=1;j<N;++j){
if(ans[i-1][j]!=inf){
ans[i][x] = min(ans[i][x],ans[i-1][j]+1);
}
b[i][x]=i;
}
int s = i-1;
while(s>=0&&ans[s][x]!=inf){
ans[i][x+1] = ans[s][x];
b[i][x+1] = b[s][x];
s = b[s][x]-1;
++x;
}
}
int r = inf;
for(int i=1;i<N;++i){
r = min(r,ans[n-1][i]);
}
cout<<r<<endl;
}
return 0;
}

1295E题解,相当精彩的线段树实例

下面代码取自:jiangly

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
#include <iostream>
#include <algorithm>
#include <vector>
class SegmentTree {
private:
int n;
std::vector<long long> min, tag;
void add(int p, long long v) {
min[p] += v;
tag[p] += v;
}
void push(int p) {
add(2 * p, tag[p]);
add(2 * p + 1, tag[p]);
tag[p] = 0;
}
void pull(int p) {
min[p] = std::min(min[2 * p], min[2 * p + 1]);
}
void rangeAdd(int p, int l, int r, int x, int y, int v) {
if (l >= y || r <= x)
return;
if (l >= x && r <= y)
return add(p, v);
int m = (l + r) / 2;
push(p);
rangeAdd(2 * p, l, m, x, y, v);
rangeAdd(2 * p + 1, m, r, x, y, v);
pull(p);
}
public:
SegmentTree(int n) : n(n), min(4 * n), tag(4 * n) {}
void rangeAdd(int l, int r, int v) {
rangeAdd(1, 0, n, l, r, v);
}
long long getMin() {
return min[1];
}
};
int n;
std::vector<int> p, a, pos;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> n;
p.resize(n);
a.resize(n);
pos.resize(n);
for (int i = 0; i < n; ++i) {
std::cin >> p[i];
--p[i];
pos[p[i]] = i;
}
for (int i = 0; i < n; ++i)
std::cin >> a[i];
SegmentTree t(n - 1);
for (int i = 0; i < n; ++i)
t.rangeAdd(pos[i], n - 1, a[pos[i]]);
long long ans = t.getMin();
for (int i = 0; i < n; ++i) {
t.rangeAdd(pos[i], n - 1, -a[pos[i]]);
t.rangeAdd(0, pos[i], a[pos[i]]);
ans = std::min(ans, t.getMin());
}
std::cout << ans << "\n";
return 0;
}