牛客练习赛122
写在前面
比赛地址:https://ac.nowcoder.com/acm/contest/75768
因为 suzt 大神在打所以也来凑一凑热闹。
A
签到。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int n, m;
std::cin >> n >> m;
while (n --) {
int c = 0;
for (int i = 1; i <= m; ++ i) {
int x; std::cin >> x;
c += (x == 1 ? 1 : -1);
}
std::cout << abs(c) << "\n";
}
return 0;
}
B
签到。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
int n, a[kN], b[kN], p[kN];
//=============================================================
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n;
for (int i = 1; i <= n; ++ i) std::cin >> a[i];
for (int i = 1; i <= n; ++ i) p[i] = 0;
int flag = 1;
for (int i = 1; i <= n; ++ i) {
std::cin >> b[i];
if (p[a[i]] == 0) p[a[i]] = b[i];
else if (p[a[i]] != b[i]) flag = 0;
}
std::cout << (flag ? "Yes\n" : "No\n");
}
return 0;
}
C
设 \(n\le m\),手玩下发现 \(n= 1\) 无解,\(n=2\) 只可以往一个方向跳,\(m \ge 4\) 时所有位置均可遍历到,于是给小范围打个表即可。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
int ans[5][5] = {
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 1, 1,
0, 0, 1, 8, 12,
0, 0, 1, 12, 16
};
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
int n, m; std::cin >> n >> m;
if (n <= 1 || m <= 1) {
std::cout << 1 << "\n";
continue;
}
if (n == 2 || m == 2) {
if (n != 2) std::swap(n, m);
std::cout << 1 + (m - 1) / 2 << "\n";
continue;
}
if (n <= 4 && m <= 4) {
std::cout << ans[n][m] << "\n";
continue;
}
std::cout << 1ll * n * m << "\n";
}
return 0;
}
D
区间 DP
发现将圆直接断开没有影响,于是在 1 处断环,问题变为给定 \(m\) 条线段,要求这些线段之间要么相互包含要么没有任何交点,求少能删多少。
可能有重复线段,先预处理 \(g_{l, r}\) 表示端点为 \(l, r\) 的线段中最大的权值。
考虑求最多能保留多少,考虑区间 DP,设 \(f_{l, r}\) 表示完全位于区间 \([l, r]\) 的线段最多能保留多大的价值,初始化 \(\forall 1\le i\le n, f_{i, i}=0\)。枚举分界点的区间 DP 转移显然只能是 \(O(n^3)\) 的,绝对跑不过去,但对于此题发现转移时仅需考虑线段的端点即可覆盖所有有贡献的分界点,于是考虑按长度枚举区间,有如下两种转移:
- \(\max(f_{l + 1, r}, f_{l, r - 1}) \rightarrow f_{l, r}\),表示不新增区间的贡献,继承更小区间的答案即可。
- 维护集合 \(s_r\) 表示右端点为 \(r\) 的左端点的数量,则 \(\forall p\in s_r,\ f_{l, p - 1} + f_{p+1, r-1} + g_{p, r}\rightarrow f_{l, r}\) 表示新增一条以 \(r\) 为端点的,且与其他线段没有交点的线段的贡献。
第二种转移总共只会进行 \(O(nm)\) 次,第一种转移每轮只会进行常数次,则总时间复杂度 \(O(n^2 + nm)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pii std::pair<int,int>
#define mp std::make_pair
const int kN = 2e3 + 10;
const LL kInf = 1e18 + 2077;
//=============================================================
int n, m;
// struct Line {
// int l, r, len, w;
// } line[kN];
LL s, f[kN][kN];
std::map <pii, pii> maxw;
std::vector <int> v[kN];
//=============================================================
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
std::cin >> n >> m;
for (int i = 1; i <= m; ++ i) {
int l, r, w; std::cin >> l >> r >> w;
if (l > r) std::swap(l, r);
s += 1ll * w;
// line[i] = (Line) {l, r, r - l + 1, w};
if (maxw.count(mp(l, r))) {
if (maxw[mp(l, r)].first < w) maxw[mp(l, r)] = mp(w, l);
} else {
maxw[mp(l, r)] = mp(w, l);
}
}
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j < n; ++ j) {
if (!maxw.count(mp(j, i))) continue;
v[i].push_back(j);
}
}
for (int len = 2; len <= n; ++ len) {
for (int l = 1, r = l + len - 1; r <= n; ++ l, ++ r) {
f[l][r] = std::max(f[l + 1][r], f[l][r - 1]);
for (auto x: v[r]) {
if (x < l) continue;
f[l][r] = std::max(f[l][r], f[l][x - 1] + f[x + 1][r - 1] + maxw[mp(x, r)].first);
}
}
}
std::cout << s - f[1][n];
return 0;
}
/*
6 6
1 4 1
2 5 1
3 6 1
4 1 1
5 2 1
6 3 1
6 6
1 2 1
2 4 5
2 3 1
3 4 1
4 5 1
5 6 1
*/
E
dsu on tree,三维偏序
好玩题,感觉有点缝就是。
问题等价于求 \([l, r]\) 中的点建立的虚树叶节点数,也即 \([l, r]\) 中有多少个节点,其任意一子节点不位于 \([l, r]\) 中。
考虑记 \(\operatorname{pre}_{u}\) 表示 \(u\) 的子树中编号小于 \(u\) 的最大编号的点,记 \(\operatorname{succ}_u\) 表示 \(u\) 的子树中编号大于 \(u\) 的最小编号的点,则对于询问 \([l, r]\),若 \(u\in [l, r]\) 有贡献当且仅当:
长得很像偏序的形式,于是考虑四维空间中有 \(n\) 个点 \((i, i, \operatorname{pre}_i, \operatorname{succ}_i) (1\le i\le n)\),询问 \([l, r]\) 等价于给定四元组 \((l, r, l, r)\),点对询问有贡献当前仅当点对询问满足 \((\ge, \le , <, >)\)。
\(\operatorname{pre}\) 与 \(\operatorname{succ}\) 可以 dsu 对子树维护 set 查询前驱后继 \(O(n\log^2 n)\) 地求得,但是四维偏序总不能直接大力求呃呃,但是发现点中有两维是相同的,于是考虑将点降维看做 \((i, \operatorname{pre}_i, \operatorname{succ}_i)\),将一次询问中对应两维拆出来变成两次询问 \((l - 1, l, r)\),\((r, l, r)\),作差即为答案。于是转化为三维偏序 \(O(n\log ^2 n)\) 地求解即可。
注意根据题意特判 \(l=r\) 时答案为 0。
代码中使用了 cdq 分治实现三维偏序,总时间复杂度 \(O(n\log^2 n)\) 级别。
特别强调:https://blog.csdn.net/mountain_hua/article/details/115790311:
set自带的lower_bound和upper_bound的时间复杂度为 \(O(logn)\)。- 但使用 algorithm 库中的
lower_bound和upper_bound函数对set中的元素进行查询,时间复杂度为 \(O(n)\)。 - 因为 algorithm 库中的 lower_bound 和 upper_bound 时间复杂度为 \(O(logn)\) 时需要传入的迭代器为随机方位迭代器。
- 但
set,multiset这种有序容器其迭代器仅为双向迭代器,要用其自带的lower_bound和upper_bound的时间复杂度才为 \(O(logn)\)。
被特判和这个鲨了 T 了两天呃呃,第一发改了改就过了呃呃
同时学习了 cdq 分治的牛逼写法,自己 YY 的把询问和点全扔一块常数大的一批呃呃。
//dsu on tree,三维偏序
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
int n, m;
std::vector <int> v[kN];
int nodenum, sz[kN], son[kN], node[kN], L[kN], R[kN];
int pre[kN], ne[kN];
std::set <int> s;
struct Point {
int x, y, z, id, type;
} pt[kN];
std::vector <Point> q;
int ans[kN];
//=============================================================
void add(int pos_) {
s.insert(pos_);
}
void del(int pos_) {
s.erase(pos_);
}
void Query(int pos_) {
// std::set <int>::iterator it = std::lower_bound(s.begin(), s.end(), pos_);
std::set <int>::iterator it = s.lower_bound(pos_);
ne[pos_] = *it;
-- it, pre[pos_] = *it;
}
void Dfs1(int u_, int fa_) {
sz[u_] = 1;
L[u_] = ++ nodenum;
node[nodenum] = u_;
for (auto v_: v[u_]) {
if (v_ == fa_) continue;
Dfs1(v_, u_);
sz[u_] += sz[v_];
if (sz[v_] > sz[son[u_]]) son[u_] = v_;
}
R[u_] = nodenum;
}
void Dfs2(int u_, int fa_, bool son_) {
for (auto v_: v[u_]) {
if (v_ == fa_ || v_ == son[u_]) continue;
Dfs2(v_, u_, 0);
}
if (son[u_]) {
Dfs2(son[u_], u_, 1);
add(son[u_]);
}
for (auto v_: v[u_]) {
if (v_ == fa_ || v_ == son[u_]) continue;
for (int i = L[v_]; i <= R[v_]; ++ i) {
add(node[i]);
}
}
Query(u_);
if (!son_) {
for (int i = L[u_] + 1; i <= R[u_]; ++ i) {
del(node[i]);
}
}
}
namespace Bit {
#define lowbit(x) ((x)&(-x))
const int kNode = kN;
int lim, t[kNode];
void Init(int lim_) {
lim = lim_;
}
void Insert(int pos_, int val_) {
for (int i = pos_; i <= lim; i += lowbit(i)) {
t[i] += val_;
}
}
int Sum(int pos_) {
int ret = 0;
for (int i = pos_; i; i -= lowbit(i)) ret += t[i];
return ret;
}
int Query(int L_, int R_) {
return Sum(R_) - Sum(L_ - 1);
}
#undef lowbit
}
bool cmpx(const Point &fir_, const Point &sec_) {
return fir_.x < sec_.x;
}
bool cmpy(const Point &fir_, const Point &sec_) {
return fir_.y < sec_.y;
}
void Cdq(int L_, int R_, std::vector<Point> &q_) {
if (L_ == R_) {
std::sort(q_.begin(), q_.end(), cmpy);
for (auto& x: q_) {
if (pt[L_].y < x.y && x.z < pt[L_].z) {
ans[x.id] += x.type;
}
}
return ;
}
int mid = (L_ + R_) >> 1;
std::vector <Point> lft, rgt;
for (auto& x: q_) {
if (x.x > mid) rgt.push_back(x);
else lft.push_back(x);
}
Cdq(L_, mid, lft), Cdq(mid + 1, R_, rgt);
int i = L_;
for (auto& x: rgt) {
for (; pt[i].y < x.y && i <= mid; ++ i) Bit::Insert(pt[i].z, 1);
ans[x.id] += x.type * Bit::Query(x.z + 1, n + 1);
}
for (int k = L_; k < i; ++ k) Bit::Insert(pt[k].z, -1);
std::inplace_merge(pt + L_, pt + mid + 1, pt + R_ + 1, cmpy);
std::merge(lft.begin(), lft.end(), rgt.begin(), rgt.end(), q_.begin(), cmpy);
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
std::cin >> n >> m;
for (int i = 1; i < n; ++ i) {
int u_, v_; std::cin >> u_ >> v_;
v[u_].push_back(v_), v[v_].push_back(u_);
}
s.insert(0), s.insert(n + 1);
Dfs1(1, 0), Dfs2(1, 1, 0);
for (int i = 1; i <= n; ++ i) {
pt[i] = (Point) {i, pre[i], ne[i], 0, 0};
}
for (int i = 1; i <= m; ++ i) {
int l_, r_; std::cin >> l_ >> r_;
if (l_ == r_) {
ans[i] = 0;
continue;
}
if (l_ != 1) q.push_back((Point) {l_ - 1, l_, r_, i, -1});
q.push_back((Point) {r_, l_, r_, i, 1});
}
std::sort(q.begin(), q.end(), cmpx);
Bit::Init(n + 1);
Cdq(1, n, q);
for (int i = 1; i <= m; ++ i) std::cout << ans[i] << "\n";
return 0;
}
/*
2 1
1 2
1 2
*/
F
网络流,课上写。
写在最后
学到了什么
- D:我草区间 DP 有 \(O(n^2)\) 的太牛逼了,考虑区间合并时根据性质仅考虑有贡献的区间分界点。
- E:四维数点,但是点里有两维是相同的,则可考虑将询问的这两维拆开分别求贡献再作差,转化为三维偏序。
特别强调:https://blog.csdn.net/mountain_hua/article/details/115790311:
set自带的lower_bound和upper_bound的时间复杂度为 \(O(logn)\)。- 但使用 algorithm 库中的
lower_bound和upper_bound函数对set中的元素进行查询,时间复杂度为 \(O(n)\)。 - 因为 algorithm 库中的 lower_bound 和 upper_bound 时间复杂度为 \(O(logn)\) 时需要传入的迭代器为随机方位迭代器。
- 但
set,multiset这种有序容器其迭代器仅为双向迭代器,要用其自带的lower_bound和upper_bound的时间复杂度才为 \(O(logn)\)。

浙公网安备 33010602011771号