A
算法标签: 模拟
#include <iostream>
#include <algorithm>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
double val;
cin >> val;
if (val >= 38.0) cout << 1 << "\n";
else if (val >= 37.5) cout << 2 << "\n";
else cout << 3 << "\n";
return 0;
}
B
算法标签: 贪心
#include <iostream>
#include <string>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
string s;
cin >> s;
int n = s.size();
int res = 0;
int i = 0;
while (i < n) {
if ((i + res) % 2 == 0) {
if (s[i] == 'o') res++;
else i++;
}
else {
if (s[i] == 'i') res++;
else i++;
}
}
if ((n + res) % 2 != 0) res++;
cout << res << "\n";
return 0;
}
C
算法标签: 哈希表, 前缀和
#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
const int N = 3e5 + 10;
int n;
int w[N];
unordered_map<int, int> m1, m2;
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> w[i];
if (m2.count(w[i])) m2[w[i]]++;
else m2.insert({w[i], 1});
}
int res = 1;
for (int i = 1; i <= n; ++i) {
if (m1.count(w[i])) m1[w[i]]++;
else m1.insert({w[i], 1});
m2[w[i]]--;
if (m2[w[i]] == 0) m2.erase(w[i]);
res = max(res, (int) m1.size() + (int) m2.size());
}
cout << res << "\n";
return 0;
}
前缀后缀统计不同数字出现的次数
左右开两个桶, f [ i ] f[i] f[i]表示从开始位置到 i i i的不同数字的个数, g [ i ] g[i] g[i], 代表从后面开始向前的不同数字的个数
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 3e5 + 10;
int n;
int w[N];
int pre[N], suff[N];
bool vis[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n;
for (int i = 1; i <= n; ++i) cin >> w[i];
for (int i = 1; i <= n; ++i) {
pre[i] = pre[i - 1];
if (!vis[w[i]]) pre[i]++;
vis[w[i]] = true;
}
memset(vis, false, sizeof vis);
for (int i = n; i; --i) {
suff[i] = suff[i + 1];
if (!vis[w[i]]) suff[i]++;
vis[w[i]] = true;
}
int res = 0;
// 枚举分割点
for (int i = 1; i <= n; ++i) {
int val = pre[i - 1] + suff[i];
res = max(res, val);
}
cout << res << "\n";
return 0;
}
D
算法标签: 整数二分, 数学知识
根据立方差公式,
d
=
x
−
y
d = x - y
d=x−y,
d
d
d满足的性质是
d
∣
N
d | N
d∣N
带入原式
(
y
+
d
)
3
−
y
3
=
N
(y + d) ^ 3 - y ^ 3 = N
(y+d)3−y3=N
化简得到
3
y
2
d
+
3
y
d
2
+
d
3
=
N
3y^2d + 3yd^2 + d^3 = N
3y2d+3yd2+d3=N
d
3
≤
N
d ^ 3 \le N
d3≤N, 得到
d
d
d的范围
1
0
6
10 ^ 6
106, 求根公式直接求解一元二次方程, 时间复杂度
N
3
\sqrt[3]{N}
3N
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int solve(LL a, LL b, LL c) {
// ay^2 + by + c = 0
LL l = 0, r = 1e9;
while (l < r) {
int mid = l + r + 1 >> 1;
if (a * mid * mid + b * mid + c > 0) r = mid - 1;
else l = mid;
}
if (a * l * l + b * l + c == 0) return l;
return -1;
}
int main() {
LL n;
cin >> n;
for (LL d = 1; d * d * d <= n; ++d) {
if (n % d) continue;
LL l = solve(3, 3 * d, d * d - n / d);
if (l > 0) {
cout << l + d << " " << l << "\n";
return 0;
}
}
cout << -1 << "\n";
return 0;
}
E
算法标签: 树形 d p dp dp
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 2e5 + 10, M = N << 1;
int n, k;
int head[N], edge_end[M], next_edge[M], edge_index;
int sz[N];
bool tag = true;
void add(int ver1, int ver2) {
edge_end[edge_index] = ver2, next_edge[edge_index] = head[ver1], head[ver1] = edge_index++;
}
void No() {
cout << "No" << "\n";
exit(0);
}
void dfs(int u, int fa) {
sz[u] = 1;
int son = 0;
for (int i = head[u]; ~i; i = next_edge[i]) {
int ver = edge_end[i];
if (ver == fa) continue;
dfs(ver, u);
if (sz[ver]) {
son++;
sz[u] += sz[ver];
}
}
if (sz[u] < k) {
if (u == 1 || son > 1) No();
}
else if (sz[u] == k) {
if (son > 2) No();
// 将当前位置子树删除
sz[u] = 0;
}
else No();
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
memset(head, -1, sizeof head);
cin >> n >> k;
for (int i = 1; i < n * k; ++i) {
int u, v;
cin >> u >> v;
add(u, v);
add(v, u);
}
dfs(1, -1);
cout << "Yes" << "\n";
return 0;
}
F
算法标签: 前缀和, 线段树
切两刀的最优方案,
C
C
C题是切一刀的最优方案
如果仍然像
C
C
C一样枚举分界点, 那么时间复杂度
O
(
n
2
)
O(n ^ 2)
O(n2), 会超时, 需要将算法优化到
O
(
n
log
n
)
O(n\log {n})
O(nlogn)以下
假设固定 j j j, 枚举最优的 i i i, 最大价值如下
s u f j + 1 + max 1 ≤ i < j { p r e i + ∑ k = i + 1 j c n t k } suf_{j + 1} + \max_{1\le i < j} \left \{pre_i + \sum_{k=i + 1} ^ {j} cnt_k \right \} sufj+1+1≤i<jmax{prei+k=i+1∑jcntk}
因为枚举 j j j, 因此第一项是常量, 第二项必须在 log n \log{n} logn时间复杂度之内计算出
如何快速的计算出
p r e i + ∑ k = i + 1 j c n t k pre_i + \sum_{k = i + 1} ^ {j} cnt_k prei+k=i+1∑jcntk
第一项 p r e 1 + [ 2 , j ] pre_1 + [2, j] pre1+[2,j], 第二项 p r e 2 + [ 3 , j ] pre_2 + [3, j] pre2+[3,j], 第三项 p r e 3 + [ 4 , j ] pre_3 + [4, j] pre3+[4,j]…
假设对于每个
i
i
i, 都计算出这样一个数值, 对于
j
j
j的数组如上, 如果
j
j
j变为
j
+
1
j + 1
j+1上述数组会产生什么样的变化?
区间修改, 查询区间最大值, 线段树维护
考虑 j j j向后移动的过程, 发生两个变化, 首先多出一项
p r e j + ∑ k = j + 1 j + 1 c n t k pre_j + \sum_{k = j + 1}^{j + 1} cnt_k prej+k=j+1∑j+1cntk
形式化的说
井号表示区间个数, 对于每一项添加一个
a
j
+
1
a_{j + 1}
aj+1, 当前数有没有变化取决于
a
j
+
1
a_{j + 1}
aj+1在之前是否出现过
形式化的说, 第
i
i
i项有变化, 当且仅当
[
i
+
1
,
j
]
[i + 1, j]
[i+1,j]之间未出现
a
j
+
1
a_{j + 1}
aj+1
如果单当前区间未出现
a
j
+
1
a_{j + 1}
aj+1, 那么范围比当前区间小的区间也一定未出现
a
j
+
1
a_{j + 1}
aj+1, 具有一个二分的性质
序列需要支持的操作
- 维护全局最大值
- 区间加 1 1 1
- 末尾加一项, 等价于单点操作
线段树维护该数组, 时间复杂度 O ( n log n ) O(n\log n) O(nlogn)
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 3e5 + 10;
int n;
int w[N], pre[N], suff[N], vis[N], last[N];
int tr[N << 2], tag[N << 2];
void add(int u, int l, int r, int ql, int qr, int val) {
if (l >= ql && r <= qr) {
tr[u] += val;
tag[u] += val;
return;
}
tag[u << 1] += tag[u], tr[u << 1] += tag[u];
tag[u << 1 | 1] += tag[u], tr[u << 1 | 1] += tag[u];
tag[u] = 0;
int mid = l + r >> 1;
if (ql <= mid) add(u << 1, l, mid, ql, qr, val);
if (qr > mid) add(u << 1 | 1, mid + 1, r, ql, qr, val);
tr[u] = max(tr[u << 1], tr[u << 1 | 1]);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n;
for (int i = 1; i <= n; ++i) cin >> w[i];
for (int i = 1; i <= n; ++i) {
pre[i] = pre[i - 1];
last[i] = vis[w[i]];
if (!vis[w[i]]) pre[i]++;
vis[w[i]] = i;
}
memset(vis, 0, sizeof vis);
for (int i = n; i >= 0; --i) {
suff[i] = suff[i + 1];
if (!vis[w[i]]) suff[i]++;
vis[w[i]] = true;
}
int res = 0;
for (int j = 1; j < n; ++j) {
res = max(res, suff[j + 1] + tr[1]);
add(1, 1, n, j, j, pre[j]);
// 后面较小的区间未出现, 需要变化
add(1, 1, n, last[j + 1], j, 1);
}
cout << res << "\n";
return 0;
}
G
算法标签: 二分, 网络流, 分层图
最开始所有边权是 0 0 0, 选出 K K K条边, 改成边权 1 1 1, 求最短路的最大值
因为求最大值最小, 因此考虑二分答案
d
d
d, 如果要求答案
≥
d
\ge d
≥d, 至少需要修改几条边
首先讨论
d
=
1
d = 1
d=1的情况
d
=
1
d = 1
d=1也就是最短路至少是
1
1
1, 换言之就是至少需要修改几条边使得原图不连通, 修改一条边的边权可以看作将一条边割开,
d
=
1
d = 1
d=1情况下, 求最小割
最大流最小割定理证明了最大流等于最小割, 因此就是求最大流
然后讨论
d
=
2
d = 2
d=2的情况
割掉任何一条边代价都是
1
1
1,
d
=
1
d = 1
d=1判定的是是否存在最短路
d
i
s
t
=
0
dist = 0
dist=0
如果能从起点走到
2
2
2号点,
d
i
s
t
2
=
0
dist_2 = 0
dist2=0, 目标是
4
4
4号点的最短路不是
0
0
0,
d
i
s
t
4
≠
0
dist_4 \ne 0
dist4=0
建立分层图
上标代表从起点
s
s
s 走
v
0
v ^ 0
v0, 存在长度为
0
0
0的路径, 起点是
1
0
1 ^ 0
10
目标是从起点出发不能走到终点
至少割四条边, 使得
d
=
2
d = 2
d=2, 时间复杂度
O
(
N
5
M
log
N
)
O(N ^ 5M\log N)
O(N5MlogN)
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 10010, M = 200010, INF = 0x3f3f3f3f;
int n, m, k, s_node, e_node;
int head[N], edge_end[M], next_edge[M], w[M], edge_index;
int q[N], layer[N], curr[N];
int s_nodes[N], e_nodes[N];
void add(int ver1, int ver2, int val) {
edge_end[edge_index] = ver2, next_edge[edge_index] = head[ver1], w[edge_index] = val, head[ver1] = edge_index++;
}
bool bfs() {
memset(layer, -1, sizeof layer);
int h = 0, t = -1;
q[++t] = s_node;
layer[s_node] = 0;
curr[s_node] = head[s_node];
while (h <= t) {
int u = q[h++];
for (int i = head[u]; ~i; i = next_edge[i]) {
int ver = edge_end[i];
if (layer[ver] == -1 && w[i]) {
layer[ver] = layer[u] + 1;
curr[ver] = head[ver];
if (ver == e_node) return true;
q[++t] = ver;
}
}
}
return false;
}
int dfs(int u, int limit) {
if (u == e_node) return limit;
// 从当前点向后流的流量
int flow = 0;
for (int i = curr[u]; ~i && flow < limit; i = next_edge[i]) {
int ver = edge_end[i];
// i前面的边都用完了, 当前弧更新为i
curr[u] = i;
if (layer[ver] == layer[u] + 1 && w[i]) {
int val = dfs(ver, min(w[i], limit - flow));
if (!val) layer[ver] = -1;
w[i] -= val;
w[i ^ 1] += val;
flow += val;
}
}
return flow;
}
int dinic() {
int res = 0, flow;
while (bfs()) {
// 搜索增广路径并且累计全部的流量
while ((flow = dfs(s_node, INF))) {
res += flow;
}
}
return res;
}
bool check(int val) {
memset(head, -1, sizeof head);
edge_index = 0;
// 同一层的边
for (int i = 1; i <= val; ++i) {
for (int j = 1; j <= m; ++j) {
int u = s_nodes[j];
int v = e_nodes[j];
add((i - 1) * n + u, (i - 1) * n + v, 1);
add((i - 1) * n + v, (i - 1) * n + u, 0);
}
}
// 层与层之间的边
for (int i = 1; i < val; ++i) {
for (int j = 1; j <= m; ++j) {
int u = s_nodes[j];
int v = e_nodes[j];
add((i - 1) * n + u, i * n + v, 1e8);
add(i * n + v, (i - 1) * n + u, 0);
}
add((i - 1) * n + n, i * n + n, 1e8);
add(i * n + n, (i - 1) * n + n, 0);
}
// 计算最小割
s_node = 1, e_node = (val - 1) * n + n;
return dinic() <= k;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> m >> k;
for (int i = 1; i <= m; ++i) cin >> s_nodes[i] >> e_nodes[i];
int l = 0, r = n - 1;
while (l < r) {
int mid = l + r + 1 >> 1;
// 当前最短路是mid情况下, 满足修改边的数量 <= k
if (check(mid)) l = mid;
else r = mid - 1;
}
cout << l << "\n";
return 0;
}