CF300 Div.2 全场题解
T1
题面
Vitaly 有 _n_ 个不同的整数数组。Vitaly 希望将此数组分成三个非空集合,以便满足以下条件:
- 第一组所有数字的乘积小于零 ( < 0)。
- 第二组中所有数字的乘积大于零 ( > 0)。
- 第三组所有数字的乘积等于零。
- 初始数组中的每个数字必须恰好出现在一组中。
 帮助维塔利。除以给定的数组。题解分别统计小于0等于0和大于0的数,然后如果小于0的是偶数个直接丢给0一个,然后如果没有大于0的那就分给大于0的两个负数,最后去重输出即可。Code1 
 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,然后输出即可。Code1 
 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]; | 
于是本题结束了。




