2019 Multi-University Training Contest 4
Contest Info
[Practice Link](https://cn.vjudge.net/contest/313505)
Solved | A | B | C | D | E | F | G | H | I | J |
---|---|---|---|---|---|---|---|---|---|---|
5/10 | O | - | Ø | - | - | - | O | O | - | O |
- O 在比赛中通过
- Ø 赛后通过
- ! 尝试了但是失败了
- - 没有尝试
Solutions
A. AND Minimum Spanning Tree
题意:
有\(n\)个点,定义两个点之间的边权为\(x\;and\;y\),现在要求输出\(n\)的点的最小生成树的权值和,以及\([2, n]\)每个点的父亲,要求字典序最小。
思路:
最小生成树的权值和为\(1\)时,当且仅当\(n = 2^t - 1\),否则权值为\(0\)。
\([2, n]\)每个点的父亲就从低位到高位,对于点\(u\)来说,某一位二进制位为\(0\),并且这一位的数小于等于\(n\),那么就选它当父亲。
否则选\(1\)当父亲,代价为\(1\)。
代码:
#include <bits/stdc++.h>
using namespace std;
#define N 200010
int res[N];
int get(int x) {
for (int i = 0; i <= 25; ++i) {
if (((x >> i) & 1) == 0) {
return (1 << i);
}
}
}
int main() {
int T; scanf("%d", &T);
while (T--) {
int n; scanf("%d", &n);
int fee = 0;
for (int i = 2; i <= n; ++i) {
int f = get(i);
if (f <= n) {
res[i] = f;
} else {
res[i] = 1;
++fee;
}
}
printf("%d\n", fee);
for (int i = 2; i <= n; ++i)
printf("%d%c", res[i], " \n"[i == n]);
}
return 0;
}
C. Divide the Stones
题意:
有\(n\)个石头,第\(i\)个石头的重量为\(i\),现在要求将\(n\)个石头分成\(k\)堆,每堆的数量相同并且重量和也相同。
思路:
当\(k\;|\;n\)时,并且\(k\;|\;\frac{n(n + 1)}{2}\)时,能分。
我们令\(t = \frac{n}{k}\):
- \(t\)是偶数的时候,我们以两堆为一组,每次取首尾分进一堆。
- \(t\)是奇数的时候:
-
如果\(k = 1\),那么直接全给这一堆
-
如果\(t = 1\),那么当且仅当\(n = 1, k = 1\)的时候有解,并且无解的情况已经在\(k\;|\;\frac{n(n + 1)}{2}\)这个条件下叛过了
-
否则我们取出前三堆,按如下方式构造,剩下的就变成偶数堆,两两分成一组即可。
5 4 3 2 1 6 9 7 10 8 13 11 14 12 15
-
代码:
#include <bits/stdc++.h>
using namespace std;
int n, k, t;
vector <vector<int>> vec;
int main() {
int T; scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &k);
if ((1ll * n * (n + 1) / 2) % k) {
puts("no");
continue;
}
vec.clear(); vec.resize(k + 1);
t = n / k;
if (t % 2 == 0) {
for (int i = 1; i <= t; i += 2) {
int l = (i - 1) * k + 1, r = (i + 1) * k;
for (int j = 1; j <= k; ++j) {
vec[j].push_back(l);
vec[j].push_back(r);
++l; --r;
}
}
} else {
if (k == 1) {
for (int i = 1; i <= n; ++i) vec[1].push_back(i);
} else {
for (int i = 1; i <= k; ++i) vec[k - i + 1].push_back(i);
int pos = k + 1;
for (int i = 1; i <= k; i += 2) {
vec[i].push_back(pos);
++pos;
}
for (int i = 2; i <= k; i += 2) {
vec[i].push_back(pos);
++pos;
}
pos = 2 * k + 1;
for (int i = 2; i <= k; i += 2) {
vec[i].push_back(pos);
++pos;
}
for (int i = 1; i <= k; i += 2) {
vec[i].push_back(pos);
++pos;
}
for (int i = 4; i <= t; i += 2) {
int l = (i - 1) * k + 1, r = (i + 1) * k;
for (int j = 1; j <= k; ++j) {
vec[j].push_back(l);
vec[j].push_back(r);
++l; --r;
}
}
}
}
puts("yes");
for (int i = 1; i <= k; ++i)
for (int j = 0, sze = (int)vec[i].size(); j < sze; ++j)
printf("%d%c", vec[i][j], " \n"[j == sze - 1]);
}
return 0;
}
G. Just an Old Puzzle
题意:
\(4 \times 4\)的数字华容道,问\(120\)步内是否可以复原。
思路:
如果有解的情况下大概\(80\)步就可以复原。
那么只需要考虑是否有解:
- 若格子列数为奇数,则逆序数必须为偶数;
- 若格子列数为偶数,且逆序数为偶数,则当前空格所在行数与初始空格所在行数的差为偶数;
- 若格子列数为偶数,且逆序数为奇数,则当前空格所在行数与初始空格所在行数的差为奇数。
代码:
#include <bits/stdc++.h>
using namespace std;
#define N 110
int a[N], b[N];
int main() {
int t;
scanf("%d", &t);
while (t--) {
for (int i = 0; i < 16; ++i) {
scanf("%d", a + i);
}
int zero = 0;
int tot = 0;
for (int i = 0; i < 16; ++i) {
if (a[i] == 0) {
zero = i;
} else {
b[++tot] = a[i];
}
}
int res = 0;
for (int i = 1; i <= 15; ++i) {
for (int j = 1; j < i; ++j) {
if (b[j] > b[i]) {
res++;
}
}
}
zero = zero / 4 + 1;
if (res % 2 == (4 - zero) % 2) {
puts("Yes");
} else {
puts("No");
}
}
return 0;
}
H. K-th Closest Distance
题意:
有一个序列\(a_i\),每次询问区间\([l, r]\),问\(a_l \cdots a_r\)中与\(p\)的距离第\(k\)小的是多少。
距离定义为:\(|p - a_i|\)。
强制在线。
思路:
二分答案\(x\),然后找区间\([p - x, p + x]\)中有多少个数,主席树统计即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define N 100010
int n, m, D, a[N], b[N];
struct SEG {
struct node {
int sum, ls, rs;
node() {
sum = 0;
ls = rs = -1;
}
}t[N * 60];
int rt[N], cnt = 0;
void init() {
cnt = 0;
t[0] = node();
for (int i = 0; i <= n; ++i) rt[i] = 0;
}
void pushup(int id, int l, int r) {
t[id].sum = 0;
if (l != -1) t[id].sum += t[l].sum;
if (r != -1) t[id].sum += t[r].sum;
}
void update(int &now, int pre, int l, int r, int pos) {
int tmp = ++cnt;
t[tmp] = t[pre];
if (l == r) {
++t[tmp].sum;
now = tmp;
return;
}
int mid = (l + r) >> 1;
if (pos <= mid) update(t[tmp].ls, t[pre].ls, l, mid, pos);
else update(t[tmp].rs, t[pre].rs, mid + 1, r, pos);
pushup(tmp, t[tmp].ls, t[tmp].rs);
now = tmp;
}
int query(int now, int l, int r, int ql, int qr) {
if (ql > qr) return 0;
if (now == -1) return 0;
if (l >= ql && r <= qr) return t[now].sum;
int mid = (l + r) >> 1;
int res = 0;
if (ql <= mid) res += query(t[now].ls, l, mid, ql, qr);
if (qr > mid) res += query(t[now].rs, mid + 1, r, ql, qr);
return res;
}
}seg;
int check(int p, int x, int L, int R) {
int l = lower_bound(b + 1, b + 1 + b[0], p - x) - b;
l = max(1, l);
int r = upper_bound(b + 1, b + 1 + b[0], p + x) - b - 1;
r = min(D, r);
return seg.query(seg.rt[R], 1, D, l, r) - seg.query(seg.rt[L - 1], 1, D, l, r);
}
int main() {
int T; scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
seg.init();
D = 0; b[0] = 0;
for (int i = 1; i <= n; ++i) scanf("%d", a + i), b[++b[0]] = a[i];
sort(b + 1, b + 1 + b[0]);
b[0] = unique(b + 1, b + 1 + b[0]) - b - 1;
D = b[0];
for (int i = 1; i <= n; ++i) a[i] = lower_bound(b + 1, b + 1 + b[0], a[i]) - b;
for (int i = 1; i <= n; ++i) {
seg.update(seg.rt[i], seg.rt[i - 1], 1, D, a[i]);
}
int lst = 0;
int L, R, p, k;
while (m--) {
scanf("%d%d%d%d", &L, &R, &p, &k);
L ^= lst;
R ^= lst;
if (L > R) swap(L, R);
p ^= lst;
k ^= lst;
int l = 0, r = 1e6, res = -1;
while (r - l >= 0) {
int mid = (l + r) >> 1;
if (check(p, mid, L, R) >= k) {
res = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
printf("%d\n", lst = res);
}
}
return 0;
}
J. Minimal Power of Prime
题意:
给出一个数字\(n(1 \leq n \leq 10^{18})\),将\(n\)分解后为:
找出最小的\(k_i\)
思路:
考虑\(3582^5 > 10^{18}\),那么对于小于\(3582\)的质数,我们直接暴力去掉。
那么剩下的数,就只有\(x^2、x^3、x^4、x^2y^2、xy^3、x\),对于前四种类型,直接暴力开方判断,而最后一种,因为存在一个\(x\)的\(k\)是\(1\),那么最后剩下的就是这一种。
注意判断的顺序。
代码:
#include <bits/stdc++.h>
using namespace std;
#define N 5010
#define ll long long
ll n;
int prime[N], check[N], tot;
void init() {
memset(check, 0, sizeof check);
tot = 0;
for (int i = 2; i < N; ++i) {
if (!check[i]) {
prime[++tot] = i;
}
for (int j = 1; j <= tot; ++j) {
if (i * prime[j] >= N) break;
check[i * prime[j]] = 1;
if (i % prime[j] == 0) break;
}
}
}
bool f(ll x, int op) {
if (op == 1) return 1;
ll mid = pow(x, 1.0 / op);
for (ll i = max(1ll, mid - 10); i <= min(x, mid + 10); ++i) {
ll t = 1;
for (int j = 0; j < op; ++j) {
t *= i;
}
if (t == x) return 1;
}
return 0;
}
int main() {
init();
int T; scanf("%d", &T);
while (T--) {
scanf("%lld", &n);
int res = 1e9;
for (int i = 1; i <= tot; ++i) {
int x = prime[i];
if (n % x == 0) {
int tmp = 0;
while (n % x == 0) {
++tmp;
n /= x;
}
res = min(res, tmp);
}
}
if (n != 1) {
for (int i = 4; i >= 1; --i) {
if (f(n, i)) {
res = min(res, i);
break;
}
}
}
printf("%d\n", res);
}
return 0;
}