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\),那么有:

\[\begin{eqnarray*} 0 < bx - cp < b \end{eqnarray*} \]

那么变换后有:

\[\begin{eqnarray*} \frac{p}{x} < \frac{b}{c} < \frac{p}{x - 1} \end{eqnarray*} \]

那么就可以用辗转相除法解决这个式子。
如果:

\[\begin{eqnarray*} \left\lceil \frac{p}{x} \right\rceil \leq \left\lfloor \frac{p}{x - 1} \right\rfloor \end{eqnarray*} \]

那么我们直接令\(c = 1, b = \left\lceil \frac{p}{x} \right\rceil\),这样的\(b\)是最小的。
否则我们减去左边的整数部分,有:

\[\begin{eqnarray*} \frac{p \% x}{x} < \frac{b - c \cdot \frac{p}{x}}{c} < \frac{p - (x - 1) \cdot \frac{p}{x}}{x - 1} \end{eqnarray*} \]

然后再倒转过来,即:

\[\begin{eqnarray*} \frac{x - 1}{p - (x - 1) \cdot \frac{p}{x}} < \frac{c}{b - c \cdot \frac{p}{x}} < \frac{x}{p \% x} \end{eqnarray*} \]

然后递归即可。

代码:

#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\)

\[\begin{eqnarray*} c_i = a_i \oplus b_i \end{eqnarray*} \]

要求所有可能是\(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\),使得下式成立:

\[\begin{eqnarray*} \sum\limits_{i = 1}^n |a_i \cdot x + b_i| = C \end{eqnarray*} \]

思路:
考虑每一对\((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;
}
posted @ 2019-08-06 08:35  Dup4  阅读(222)  评论(0编辑  收藏  举报