Codeforces Global Round 14
A.Phoenix and Gold
题意:
给定n个数, 问按照什么顺序从左到右将这n个数加起来, 期间不会出现和为x的状态
思路:
首先求一遍数组的和, 如果等于x那么加和过程中一定会出现x这个状态,否则就将原数组从小到大排序, 然后从左到右依次加和,若中间和第一次出现了x, 那么就将当前的数先减去, 加上数组的最后一个,之后再将剩下的依次加起来即可, 如果加和过程中没有出现和为x的情况, 那么就皆大欢喜直接按照这个顺序输出即可
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e2 + 5;
int weight[N], n, x;
int f[10005];
int main()
{
int T;
cin >> T;
f[0] = 1;
while (T--) {
int sum = 0, pos = -1;
cin >> n >> x;
for (int i = 1; i <= n; i++) {
cin >> weight[i];
sum += weight[i];
}
if (sum < x) {
cout << "YES" << endl;
for (int i = 1; i <= n; i++) cout << weight[i] << " ";
cout << endl;
continue;
}
if (sum == x) {
cout << "NO" << endl;
continue;
}
sum = 0;
cout << "YES" << endl;
sort(weight + 1, weight + n + 1);
for (int i = 1; i <= n; i++) {
sum += weight[i];
if (sum == x) {
sum -= weight[i];
sum += weight[n];
cout << weight[n] << " ";
i--, n--;
continue;
}
cout << weight[i] << " ";
}
cout << endl;
}
return 0;
}
B. Phoenix and Puzzle
题意:
给n个等腰直角三角形, 问全部用完是否可以组成一个正方形
思路:
刚开始可以观察发现, 每一个正方形都可以由 2 k 2^k 2k 个等腰直角三角形构成, 以一个小正方形为基数, 最终组成的大正方形一定是由 n 2 n^2 n2 个小正方形组成的, 那么对于一个给定的 n ,只要它能够分解为 n = 2 k 2^k 2k * q 2 q^2 q2 即可, 前一项是组成一个小正方形需要的等腰直角三角形数量, 后面则是组成的 q 2 q^2 q2 个小正方形组成最后的大正方形, 所以对于奇数, 直接输出NO, 对于偶数则如下
-
对 n 除 2, 判断剩下的数字是否为平方数, 直到第一个平方数出现, 如果这个数字不是平方数且为奇数, 就输出NO
-
第二种方法就是一直对 n 除以2, 直到第一个奇数为止, 判断该奇数是否为平方数即可, 这其实是等价于第一种方式的 如果可以组成正方形 那么n一定可以分解为 2 k 2^k 2k * q 2 q^2 q2, 那么若q是偶数, 一定可以分解为 q 2 q^2 q2 = 2 t 2^t 2t * x 2 x^2 x2 (x为奇数), 也就说明如果n个等腰直角三角形可以组成正方形, 那么一定可以写成 2 k 2^k 2k * x 2 x^2 x2(x为奇数) 的形式, 如果这个奇数都不满足平方数的性质, 那么给它乘几个2都是无济于事的, 那么不妨把分解后的 2 t 2^t 2t当作组成每个小正方形的等腰三角形数量, x 2 x^2 x2 当作边 x 2 x^2 x2 个小三角形组成边长为x的大正方形, 这样是一定可以构造出一个正方形的, 且是由最少的小正方形拼接成的, 如果想要给它扩大一些的话, 从组成小正方形的等腰三角形里抽出 2 2 m 2^{2m} 22m即可(0 <= 2m <= t) 即可.
由上可以得出, 只要n满足 n = 2 t 2^t 2t * x 2 x^2 x2 的形式, 就可以将n个等腰直角三角形无剩余的构成正方形
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 1e2 + 5;
int n;
int main()
{
int T;
cin >> T;
while (T--) {
cin >> n;
bool flag = 0;
if (n % 2) flag = 1;
else {
while (n > 1) {
n /= 2;
if (n % 2) {
int t = sqrt(n);
if (t * t != n) {
flag = 1;
break;
}
else break;
}
}
}
if (flag) cout << "NO" << endl;
else cout << "YES" << endl;
}
return 0;
}
C. Phoenix and Towers
题意:
给n个高度不超过x的方块, 想要你构造出m个塔, 每个塔的高度都是组成这个塔的方块的高度和, 且这m个塔两两之间高度不能超过x, 要求一个序列,其中每一个位置是记录的该位置方块属于哪一个塔
思路:
刚开始看样例每个塔的构成部分都是连续的, 就想到了前缀和, 但是发现这样需要枚举区间, 再一算最多需要 n m n^m nm 的枚举量… 到后面才发现可以不连续…
这个题可以采用贪心的思路, 先说方法再说证明, 每一次取这个m个塔中高度最矮的塔, 给其任意加上一个方块, 直到所有方块用尽, 可以发现这种方式是永远有解的,也就是不存在"NO"的情况,不得不说, 确实坑有点深…
证明:
在每一次操作之前, 是需要保证操作之前m个塔的高度差都两两小于x, 其实初始状态下所有m个塔的高度都为0, 是满足的. 下面每一次向最矮的塔加入一个高度为h的方块, 由于这个塔是最矮的, 所以它与其它塔的高度差满足 (-x <= d <= 0) 又已知所有块的高度都小于x,那么给这个塔加上h后, 这个塔与其它塔的高度差变为 (-x <= d + h <= h < x) 依旧是满足两两高度差小于x, 保证了每一步都是两两高度差小于x, 所以这种贪心的策略就是正确的, 只需要每一次给最矮的塔加上一个方块即可
代码
#include <bits/stdc++.h>
using namespace std;
using PII = pair<int,int>;
const int N = 1e5 + 5;
int n, m, x;
int pos[N], h[N];
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--) {
priority_queue<PII>heap;
cin >> n >> m >> x;
for (int i = 1; i <= n; i++) cin >> h[i];
for (int i = 1; i <= m; i++) heap.push({0, i});
for (int i = 1; i <= n; i++) {
auto t = heap.top(); heap.pop();
t.first -= h[i], pos[i] = t.second;
heap.push(t);
}
cout << "YES" << endl;
for (int i = 1; i <= n; i++) cout << pos[i] << " ";
cout << endl;
}
return 0;
}
补 D. Phoenix and Socks
题意:
给n只袜子(保证n为偶数), 其中有l只为左袜子, r只为右袜子, 它们都有各自的颜色, 将左袜子变为右袜子或者改变一次袜子颜色将花费1, 只有两只袜子为同色且为一左一右时才可以配对, 问将这n只袜子全部配对最少需要花费多少
思路:
输入左右袜子的时候统计一下左右袜子颜色的数量, 可以发现, 对于左右颜色相等的袜子, 直接消去就可以了, 对于答案没有影响. 下面首先说一个结论, 对于n只左袜子和n只右袜子, 若任意一只左袜子都与任意一只和右袜子不同, 那么这n对袜子要想完全消除, 代价一定为n. 在把初始所有可以配对的袜子删除之后, 若左右袜子数量相等, 之间使用上面的结论就可以, 若是不相等, 就考虑给大的一方内部消去一部分袜子, 直至左右袜子数量相等, 怎么消除呢? 这时候就要贪心的把其内部颜色相同的袜子变一只然后消去, 若是把颜色不同的同方向袜子变为左右袜子, 一定是要花费代价为2, 而这样只需要代价为1. 那现在就有一个问题, 为什么我不把所有左袜子颜色相同的消去, 在把所有右袜子的颜色相同的消去 而是要删除到左右袜子相同呢?
首先, 对于将是数量较多的袜子删除颜色相同的多的一部分, 这一部分是没有异议的, 关键点就在于相等后还要不要继续把相同方向同颜色袜子删去, 假设删去一对, 那么另一方向袜子就要改变其中一只袜子方向来与剩下的自己配对, 若改变后的该袜子在原方向袜子里有相同颜色, 这样代价为1, 若没有, 还要改变它的颜色, 这样代价就变成了2, 可以发现只要达到左右袜子数量相同, 就可以根据上面的结论, 保证每一对袜子消除时的代价一定为1, 而继续删除只会使代价向增加的方向改变, 所以是不能继续删除的
在删除至相同时还有一种情况, 较多数量的袜子内部不能进行改变方向的删除, 假设最后左袜子多且数量为l, 右袜子为r, 那么只能先花 (l - r) / 2的代价将一部分多的袜子变为右袜子, 然后再花费l - (l - r) / 2的代价一一配对, 这样总花费为l, 通过这个例子, 可以发现只需要最后加上多的一方的袜子在尝试内部删除至左右袜子相等操作后剩余的数量即可 这样答案就是 在删除至左右袜子相同时改变的次数加上多的袜子一方在此操作后剩余袜子的数量即可
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 5;
int n, l, r;
int cnt1[N], cnt2[N], a[N], b[N];
void Solve()
{
cin >> n >> l >> r;
for (int i = 1; i <= n; i++) cnt1[i] = cnt2[i] = 0;
for (int i = 1; i <= l; i++) {
cin >> a[i];
cnt1[a[i]]++;
}
for (int i = 1; i <= r; i++) {
cin >> b[i];
cnt2[b[i]]++;
}
for (int i = 1; i <= n; i++) {
int x = min(cnt1[i], cnt2[i]);
cnt1[i] -= x, cnt2[i] -= x, l -= x, r -= x;
}
int ans = 0;
if (l > r) {
for (int i = 1; i <= n; i++) {
while (l != r && cnt1[i] >= 2) {
cnt1[i] -= 2;
l -= 2;
ans++;
}
if (l == r) break;
}
ans += l;
}
else {
for (int i = 1; i <= n; i++) {
while (l != r && cnt2[i] >= 2) {
cnt2[i] -= 2;
r -= 2;
ans++;
}
if (l == r) break;
}
ans += r;
}
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--) {
Solve();
}
return 0;
}