CF300 Div.2 全场题解
T1
题面
Vitaly 有 _n_ 个不同的整数数组。Vitaly 希望将此数组分成三个非空集合,以便满足以下条件:
- 第一组所有数字的乘积小于零 ( < 0)。
- 第二组中所有数字的乘积大于零 ( > 0)。
- 第三组所有数字的乘积等于零。
- 初始数组中的每个数字必须恰好出现在一组中。
帮助维塔利。除以给定的数组。题解
分别统计小于0等于0和大于0的数,然后如果小于0的是偶数个直接丢给0一个,然后如果没有大于0的那就分给大于0的两个负数,最后去重输出即可。Code
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
using namespace std;
int main() {
int n;
cin >> n;
vector<int>s(n);
set<int> a, b, c;
for (int i = 0; i < n; i++) {
cin >> s[i];
if (s[i] < 0) {
a.insert(s[i]);
} else if (s[i] > 0) {
b.insert(s[i]);
} else {
c.insert(s[i]);
}
}
if (a.size() % 2 == 0) {
int y;
for (int x : a) {
y = x;
break;
}
c.insert(y);
a.erase(y);
}
if (b.size() == 0) {
int y;
for (int x : a) {
y = x;
break;
}
b.insert(y);
a.erase(y);
for (int x : a) {
y = x;
break;
}
b.insert(y);
a.erase(y);
}
cout << a.size() << " ";
for (int i : a)cout << i << " ";
cout << endl;
cout << b.size() << " ";
for (int i : b)cout << i << " ";
cout << endl;
cout << c.size() << " ";
for (int i : c)cout << i << " ";
cout << endl;
return 0;
}T2
题面
一个编程教练有 _n_ 个学生要教。我们知道 _n_ 可以被 3 整除。假设所有学生的编号都从 1 到 _n_(含)。
在大学编程锦标赛之前,教练希望将所有学生分成三人一组。对于某些学生,我们知道他们想在同一个团队中。此外,如果第 _i_ 个学生想和第 _j_ 个学生在同一个团队,那么第 _j_ 个学生想和第 _i_ 个学生在同一个团队。教练希望球队表现出良好的成绩,因此他希望满足以下条件:如果第 _i_ 个学生想与_第 j_ 个学生在同一个团队中,那么第 _i_ 个学生和第 _j_ 个学生必须在同一个团队中。此外,很明显,每个学生必须只属于一个团队。
帮助教练并按照他想要的方式划分球队。题解
看到分组直接想并查集,直接判断是否能恰好分完,然后我们分讨3个全的,2个的,1个落单的,这些情况能凑的凑,3个的不用管,最后多余了直接-1,然后输出即可。Code
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
using namespace std;
int fa[114], sz[114];
int n, m;
int find(int x) {
if (fa[x] != x) {
fa[x] = find(fa[x]);
}
return fa[x];
}
void hb(int x, int y) {
x = find(x);
y = find(y);
if (x == y) return;
if (sz[x] < sz[y]) swap(x, y);
fa[y] = x;
sz[x] += sz[y];
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
fa[i] = i;
sz[i] = 1;
}
for (int i = 0; i < m; i++) {
int a, b;
cin >> a >> b;
hb(a, b);
}
for (int i = 1; i <= n; i++) {
find(i);
}
map<int, vector<int >> mp;
for (int i = 1; i <= n; i++) {
mp[fa[i]].push_back(i);
}
vector<int> one;
vector<vector<int >> two;
vector<vector<int >> ret;
bool f = 1;
for (auto i : mp) {
int size = i.second.size();
if (size > 3) {
f = 0;
break;
} else if (size == 1) {
one.push_back(i.second[0]);
} else if (size == 2) {
two.push_back(i.second);
} else if (size == 3) {
ret.push_back(i.second);
}
}
if (!f || two.size() > one.size()) {
cout << -1;
return 0;
}
for (int i = 0; i < two.size(); i++) {
vector<int> zu = two[i];
zu.push_back(one.back());
one.pop_back();
ret.push_back(zu);
}
for (int i = 0; i < one.size(); i += 3) {
ret.push_back({one[i], one[i + 1], one[i + 2]});
}
for (auto zu : ret) {
for (int i = 0; i < 3; i++) {
cout << zu[i] << " ";
}
cout << endl;
}
return 0;
}
R1T1:
题意:
给你好几种种类的物品,每个物品有个体积和价值,但是购买某种种类之前需要先支付一个前置代价,问你最多能获得多少价值。
题解:
一个依赖背包的模版题,我们设 $dp_{i,j}$ 表示当前选了 $i$ 种类物品,花费 $j$ 能获得的价值。我们可以先处理出选择当前这一组支付代价的情况,然后进行模版 $0/1$ 背包就可以了。
Code:
1 |
|
R1T4:
题意:
每个节点有个点权,每条边有边权,每个点有个松鼠,你需要把某些点权运送到各个点,然后让所有点权的方差最小。
题解:
方差最小即为尽量平均分配,那么最优情况肯定是 $sum(mod)n$ 个多出来的点,那么我们不妨设 $dp_{i,j}$ 为以 $i$ 为子树,$j$ 个点权为 $sum(mod)n$ 个点的最小操作代价。考虑树形背包,假设前面和下面都算好了,对于一个新的点 $v$,我们有 $dp_{now,i+j}=dp_{v,j}+dp_{now,i}+pnum\times w$ 其中,$pnum=abs(ave\times (sz[now]+sz[v])-a[now]-a[v]+i+j)-abs(ave\times sz[now]-a[now]+i)$。不难理解,我们给之前点分配 $i$ 容量,当前的 $v$ 分配 $j$ 容量,转移只需要加上边的经过次数乘边权即可。所以答案就是 $dp_{1,sum(mod)n}$,即根节点的价值。
Code:
1 |
|
E - Empire Strikes Back
题意
求满足下式的最小 $n$ :
题解
显然 $n$ 有单调性,考虑二分和 Check,不难有初版代码:
1 | int cnt[MaxN]; |
很显然 cnt
的初始化出锅了,考虑优化。于是平铺阶乘整体计算:
1 | for(int i = MaxN - 10; i >= 1; i--) cnt[i] += cnt[i+1]; |
于是本题结束了。