2019 Multi-University Training Contest 5
Contest Info
[Practice Link](https://cn.vjudge.net/contest/313506)
Solved | A | B | C | D | E | F | G | H | I | J |
---|---|---|---|---|---|---|---|---|---|---|
7/10 | Ø | O | - | O | O | O | O | - | - | Ø |
- O 在比赛中通过
- Ø 赛后通过
- ! 尝试了但是失败了
- - 没有尝试
Solutions
A. fraction
题意:
给定一个\(p\)和\(x\),要求找到一个最小的\(b\),满足\(a < b\)并且\(a \equiv bx \bmod p\)
思路:
考虑\(a \equiv bx \bmod p\)等价于\(a = bx - cp\),那么有:
那么变换后有:
那么就可以用辗转相除法解决这个式子。
如果:
那么我们直接令\(c = 1, b = \left\lceil \frac{p}{x} \right\rceil\),这样的\(b\)是最小的。
否则我们减去左边的整数部分,有:
然后再倒转过来,即:
然后递归即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll p, a, b, x, y;
void gao(ll pa, ll pb, ll qa, ll qb, ll &x, ll &y) {
ll z = (pa + pb - 1) / pb;
if (z <= qa / qb) {
x = z, y = 1;
return;
}
pa -= (z - 1) * pb;
qa -= (z - 1) * qb;
gao(qb, qa, pb, pa, y, x);
x += (z - 1) * y;
}
int main() {
int T; scanf("%d", &T);
while (T--) {
scanf("%lld%lld", &p, &x);
gao(p, x, p, x - 1, b, y);
a = b * x - p * y;
printf("%lld/%lld\n", a, b);
}
return 0;
}
B. three arrays
题意:
给出两个序列\(a_i, b_i\),要求重新排列生成序列\(c_i\):
要求所有可能是\(c_i\)序列中,输出字典序最小的那个。
思路:
考虑建立两个\(Trie\)树,我们从高位往低位走的时候,先走\((0, 0)、(1, 1)\)再走\((1, 0)、(0, 1)\)。
那么\((0, 0)\)和\((1, 1)\)之间谁先走?
无所谓,这是两条不相干的路径。
然后生成\(n\)个数,排序一下输出即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 200010
#define pii pair <int, int>
#define fi first
#define se second
int n, a[N], b[N], res[N];
struct Trie {
struct node {
int son[2];
int tot;
node() {
memset(son, -1, sizeof son);
tot = 0;
}
}t[N * 32];
int cnt;
void init() {
cnt = 1;
t[1] = node();
}
void insert(int x, int y) {
int root = 1;
for (int i = 30; i >= 0; --i) {
int &nx = t[root].son[(x >> i) & 1];
if (nx == -1) {
nx = ++cnt;
t[nx] = node();
}
root = nx;
t[root].tot += y;
}
}
void del(int x, int y) {
int root = 1;
for (int i = 30; i >= 0; --i) {
int nx = t[root].son[(x >> i) & 1];
if (nx == -1) {
return;
}
t[nx].tot -= y;
root = nx;
}
}
int query(int x) {
int root = 1;
ll res = 0;
for (int i = 30; i >= 0; --i) {
int f = (x >> i) & 1;
int nx = t[root].son[f];
if (nx == -1 || t[nx].tot == 0) {
res |= (1 << i);
nx = t[root].son[!f];
}
root = nx;
}
return res;
}
}trie[2];
int Move[4][2] = {
0, 0,
1, 1,
0, 1,
1, 0
};
int query() {
int num[2] = {0, 0};
int it[2] = {1, 1};
int nx[2];
for (int i = 30; i >= 0; --i) {
for (int j = 0; j < 4; ++j) {
nx[0] = trie[0].t[it[0]].son[Move[j][0]];
nx[1] = trie[1].t[it[1]].son[Move[j][1]];
if (nx[0] != -1 && nx[1] != -1 && trie[0].t[nx[0]].tot > 0 && trie[1].t[nx[1]].tot > 0) {
if (Move[j][0]) num[0] |= (1 << i);
if (Move[j][1]) num[1] |= (1 << i);
it[0] = nx[0];
it[1] = nx[1];
// printf("%d %d\n", Move[j][0], Move[j][1]);
break;
}
}
}
trie[0].del(num[0], 1);
trie[1].del(num[1], 1);
// printf("%d %d\n", num[0], num[1]);
return num[0] ^ num[1];
}
int main() {
int T; scanf("%d", &T);
while (T--) {
scanf("%d", &n);
trie[0].init(); trie[1].init();
for (int i = 1; i <= n; ++i) {
scanf("%d", a + i);
trie[0].insert(a[i], 1);
}
for (int i = 1; i <= n; ++i) {
scanf("%d", b + i);
trie[1].insert(b[i], 1);
}
for (int i = 1; i <= n; ++i) {
res[i] = query();
}
sort(res + 1, res + 1 + n);
for (int i = 1; i <= n; ++i)
printf("%d%c", res[i], " \n"[i == n]);
}
return 0;
}
D. equation
题意:
给出一个\(a_i, b_i\),要求找出所有可能的\(x\),使得下式成立:
思路:
考虑每一对\((a_i, b_i)\)都是一条折线,那么将这些折线按零点排序,那么就知道绝对值的取法,然后判断算出来的\(x\)是否在区域内即可。
或者判断是否存在\(\sum a_i = 0\),并且\(\sum b_i = C\),这时候整个式子的取值与\(x\)无关,有无穷多解。
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll gcd(ll a, ll b) {
return b ? gcd(b, a % b) : a;
}
struct frac{
ll x,y;
void sim() {
ll g = gcd(abs(x), abs(y));
x /= g;
y /= g;
if (x * y == 0) {
x = 0;
y = 1;
}
if (x * y < 0) {
x = -abs(x);
y = abs(y);
} else {
x = abs(x);
y = abs(y);
}
}
frac() {}
frac(ll x, ll y) : x(x), y(y) {sim();}
frac operator+(const frac &u){
ll p, q;
p = x * u.y + y * u.x;
q = u.y * y;
ll d = gcd(p, q);
p /= d; q /= d;
return (frac){p, q};
}
frac operator-(const frac &u){
ll p, q;
p = x * u.y - y * u.x;
q = u.y * y;
ll d = gcd(p, q);
p /= d; q /= d;
return (frac){p, q};
}
frac operator*(const frac &u){
ll p, q;
p = u.x * x;
q = u.y * y;
ll d = gcd(p, q);
p /= d; q /= d;
return (frac){p, q};
}
frac operator/(const frac &u){
ll p, q;
p = u.y * x;
q = u.x * y;
ll d = gcd(p,q);
p /= d; q /= d;
return (frac){p,q};
}
bool operator < (const frac &other) const {
return x * other.y < y * other.x;
}
bool operator != (const frac &other) const {
return x * other.y != y * other.x;
}
bool operator <= (const frac &other) const {
return x * other.y <= y * other.x;
}
bool operator >= (const frac &other) const {
return x * other.y >= y * other.x;
}
bool operator == (const frac &other) const {
return x * other.y == y * other.x;
}
void sqr() {
*this = (*this) * (*this);
}
void print(){
sim();
if (x * y < 0) putchar('-');
printf("%lld/%lld", abs(x), abs(y));
}
};
const int N = 2e5 + 10;
int n;
ll C;
struct node {
int a, b;
frac x;
void scan() {
scanf("%d%d", &a, &b);
x = frac(-b, a);
}
bool operator < (const node &other) const {
return x < other.x;
}
}a[N];
frac res[N << 1]; int cnt = 0;
void solve() {
cnt = 0;
ll suma[2] = {0, 0}, sumb[2] = {0, 0};
for (int i = 1; i <= n; ++i) {
suma[1] += a[i].a;
sumb[1] += a[i].b;
}
for (int i = 1, j; i < n; i = j + 1) {
for (j = i; j < n; ++j) {
if (a[i].x != a[j + 1].x) break;
}
j = min(j, n - 1);
for (int k = i; k <= j; ++k) {
suma[1] -= a[k].a;
sumb[1] -= a[k].b;
suma[0] += a[k].a;
sumb[0] += a[k].b;
}
if (suma[0] - suma[1] == 0 && sumb[0] - sumb[1] == C) {
puts("-1");
return;
}
if (suma[1] == suma[0]) continue;
frac x = frac(C - sumb[0] + sumb[1], suma[0] - suma[1]);
if (x >= a[i].x) {
if (j + 1 < n) {
if (x < a[j + 1].x) {
res[++cnt] = x;
}
} else {
res[++cnt] = x;
}
}
}
sort(res + 1, res + 1 + cnt);
cnt = unique(res + 1, res + 1 + cnt) - res - 1;
printf("%d", cnt);
for (int i = 1; i <= cnt; ++i) {
putchar(' ');
res[i].print();
}
puts("");
}
int main() {
int T; scanf("%d", &T);
while (T--) {
scanf("%d%lld", &n, &C);
for (int i = 1; i <= n; ++i) a[i].scan();
++n; a[n].a = a[n].b = 0; a[n].x = frac(-2e9, 1);
sort(a + 1, a + 1 + n);
++n; a[n].a = a[n].b = 0; a[n].x = frac(2e9, 1);
solve();
}
return 0;
}
E - permutation 1
题意:
要求构造一个\(n\)个数的排列,使得它的差分序列是所有排列的差分序列中属于第\(k\)小的位置。
思路:
考虑贪心枚举差分序列的前缀,判断有多少个合法的排列属于这个前缀,然后决定当前位置选择哪一个即可。
因为一个排列的前缀可以由第一个数和其差分序列决定,那么直接枚举第一个数是多少即可。
时间复杂度\(\mathcal{O}(n^4)\)
代码:
#include <bits/stdc++.h>
using namespace std;
#define N 50
#define ll long long
int n, k, a[N], vis[N];
ll fac[N];
bool check(int x, int now) {
for (int i = 1; i <= n; ++i) vis[i] = 0;
vis[now] = 1;
for (int i = 1; i <= x; ++i) {
int nx = now + a[i];
if (nx < 1 || nx > n) return 0;
if (vis[nx]) return 0;
vis[nx] = 1;
now = nx;
}
return 1;
}
ll work(int x) {
ll res = 0;
for (int i = 1; i <= n; ++i) {
if (check(x, i)) {
res += fac[n - x - 1];
}
}
return res;
}
void out() {
for (int i = 1; i <= n; ++i) {
if (check(n - 1, i)) {
vector <int> vec;
int now = i;
vec.push_back(now);
for (int j = 1; j < n; ++j) {
now += a[j];
vec.push_back(now);
}
for (int j = 0; j < n; ++j)
printf("%d%c", vec[j], " \n"[j == n - 1]);
return;
}
}
}
int main() {
fac[0] = 1;
for (int i = 1; i <= 20; ++i)
fac[i] = fac[i - 1] * i;
int T; scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &k);
for (int i = 1; i < n; ++i) {
ll tot = 0, now = 0;
for (int j = -n + 1; j < n; ++j) {
a[i] = j;
now = work(i);
if (tot + now >= k) {
k -= tot;
break;
} else {
tot += now;
}
}
}
// for (int i = 1; i < n; ++i)
// printf("%d%c", a[i], " \n"[i == n - 1]);
out();
}
return 0;
}
F. string matching
题意:
询问\(\forall i \in [0, len - 1]\)中\(s[i \cdots len - 1]\)与\(s[0 \cdots len - 1]\)的暴力求\(lcp\)过程需要比较多少次。
G. permutation 2
题意:
问有多少\(n\)个数的排列满足下列要求:
- \(p_1 = x\)
- \(p_n = y\)
- \(\forall i \in [1, n - 1]\)都满足\(|p_i - p_{i + 1}| \leq 2\)
思路:
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll p = 998244353;
#define N 100010
int n, x, y;
ll a[N];
void Init() {
a[0] = a[1] = a[2] = 1;
for (int i = 3; i < N; ++i) {
a[i] = (a[i - 1] + a[i - 3]) % p;
}
}
int main() {
// freopen("input.txt", "r", stdin);
Init();
int T;
scanf("%d", &T);
while (T--) {
scanf("%d %d %d", &n, &x, &y);
if (x == 1 && y == n) {
printf("%lld\n", a[y - x]);
continue;
}
if (x == 1 || y == n) {
printf("%lld\n", a[y - x - 1]);
continue;
}
if (y - x != 1) {
printf("%lld\n", a[y - x - 2]);
} else {
if (x == 1) {
puts("1");
} else {
puts("0");
}
}
}
return 0;
}
J. find hidden array
题意:
有一个长度为\(2n\)的排列,刚开始将前\(n\)个数插入一个\(set\),然后后\(n\)的数的操作如下:
- 选取\(set\)中最大的数丢掉,然后将第\(n + i\)个数插入
- 选取\(set\)中最小的数丢掉,然后将第\(n + i\)个数插入
然后告诉你后\(n\)个操作,\(+x\)表示取出的\(x\)是最大的数, \(-x\)表示取出的\(x\)是最小的数
然后还原这个序列
思路:
显然在后\(n\)个操作中出现过的数的可选区间右界是\(n + i - 1\),然后可以用单调栈+二分搞出左界。
对于没有出现过的数可选区间右界是\(2n\)。
如果是任意解的话可以直接贪心。
那要求的是字典序最小的解,题解说也可以贪心。。。。
做法如下:
- 从左往右贪心选取每个数
- 如果有那个数的\(deadline\)到了,那么就选这个数
- 否则就选可选的最小的数
代码:
#include <bits/stdc++.h>
using namespace std;
#define N 100010
#define INF 0x3f3f3f3f
#define pii pair <int, int>
#define fi first
#define se second
int n, b[N], used[N], res[N];
vector <vector<pii>> vec, G;
struct Stack {
int a[N], top;
void init() { top = 0; }
bool empty() { return !top; }
void add(int x) { a[++top] = x; }
void pop() { --top; }
int back() { return a[top]; }
bool check(int x, int i) {
return abs(b[a[i]]) < x;
}
bool check2(int x, int i) {
return abs(b[a[i]]) > x;
}
int get(int x, int op) {
int l = 1, r = top, res = -INF;
while (r - l >= 0) {
int mid = (l + r) >> 1;
if ((op ? check2(x, mid) : check(x, mid))) {
l = mid + 1;
res = a[mid];
} else {
r = mid - 1;
}
}
return res;
}
}stk[2];
//0 表示b[i] > 0 维护单调上升序列
//1 表示b[i] < 0 维护单调下降序列
int get_left_bound(int x) {
int res = 1;
res = max(res, stk[0].get(x, 0) + n);
res = max(res, stk[1].get(x, 1) + n);
return res;
}
bool invalid() {
for (int i = 1; i <= 2 * n; ++i) if (!vec[i].empty()) {
sort(vec[i].begin(), vec[i].end());
}
multiset <int> se;
int lst = 1;
for (int i = 1; i <= 2 * n; ++i) {
if (lst < i) return 1;
for (auto it : vec[i]) {
se.insert(it.first);
}
if (*se.begin() < lst) return 1;
se.erase(se.begin());
++lst;
}
return 0;
}
void solve() {
stk[0].init(); stk[1].init();
for (int i = 1; i <= 2 * n; ++i) used[i] = 0;
vec.clear(); vec.resize(n * 2 + 10);
for (int i = 1; i <= n; ++i) {
used[abs(b[i])] = 1;
vec[get_left_bound(abs(b[i]))].push_back(pii(n + i - 1, abs(b[i])));
if (b[i] > 0) {
while (!stk[0].empty() && abs(b[stk[0].back()]) > abs(b[i])) {
stk[0].pop();
}
stk[0].add(i);
} else {
while (!stk[1].empty() && abs(b[stk[1].back()]) < abs(b[i])) {
stk[1].pop();
}
stk[1].add(i);
}
}
for (int i = 1; i <= 2 * n; ++i) if (!used[i]) {
vec[get_left_bound(i)].push_back(pii(2 * n, i));
}
if (invalid()) puts("-1");
else {
multiset <int> se;
G.clear(); G.resize(n * 2 + 10);
for (int i = 1; i <= 2 * n; ++i) {
for (auto it : vec[i])
G[it.fi].push_back(it), se.insert(it.se);
int tmp = -1;
for (auto it : G[i]) {
if ((*se.lower_bound(it.se)) == it.se) {
assert(tmp == -1);
tmp = it.se;
se.erase(it.se);
}
}
if (tmp == -1) {
tmp = *se.begin();
se.erase(se.begin());
}
printf("%d%c", tmp, " \n"[i == 2 * n]);
}
}
}
int main() {
int T; scanf("%d", &T);
while (T--) {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", b + i);
solve();
}
return 0;
}