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\)分解后为:

\[\begin{eqnarray*} n = p_1^{k_1}p_2^{k_2} \cdots p_l^{k_l} \end{eqnarray*} \]

找出最小的\(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;
}
posted @ 2019-08-01 08:00  Dup4  阅读(220)  评论(0编辑  收藏  举报