2020, XIII Samara Regional Intercollegiate Programming Contest 题解

2020, XIII Samara Regional Intercollegiate Programming Contest 题解

A. Array's Hash

题意\(Vasya\) 发明了一种新的散列函数,给定一个数组 \(a_n\) ,这个散列函数每次将数组中的前两个元素替换为一个值为 \(a_2 - a_1\) 的元素,直到只剩下一个元素,得到的这个元素即为新的哈希值。现在有 \(q\) 次询问,每次询问前该数组都先执行一个区间加操作,然后询问该数组的哈希值。

分析:这个新的哈希值就是 \(a_n-a_{n-1}+a_{n-2}-a_{n-3}+a_{n-4}\cdots\) ,因此我们直接将奇偶下标各看作一个数组进行操作即可。实际上可以 \(O(n)\) 做,不过这里偷懒直接复制了个树状数组板子。

#include <bits/stdc++.h>
#define int long long
#define SIZE 1000010
using namespace std;
int n;

namespace FenwickTree {
	int ta1[SIZE], ta2[SIZE], tb1[SIZE], tb2[SIZE];

	int lowbit(int x) { return x & (-x); }

	void add(int pos, int x, int t[]) { // 单点修改
		for (; pos <= n; pos += lowbit(pos)) {
			t[pos] += x;
		}
	}

	void add(int l, int r, int x, int t1[], int t2[]) { // [l, r] 区间+x
		add(l, x, t1);
		add(r + 1, -x, t1);
		add(l, l * x, t2);
		add(r + 1, -x * (r + 1), t2);
	}

	int query_presum(int pos, int t[]) { // 单点查询,即对差分数组求前缀和
		int ans = 0;
		for (; pos > 0; pos -= lowbit(pos)) {
			ans += t[pos];
		}
		return ans;
	}

	int query_sum(int l, int r, int t1[], int t2[]) { // 区间修改下的区间查询
		int p1 = l * query_presum(l - 1, t1) - query_presum(l - 1, t2);
		int p2 = (r + 1) * query_presum(r, t1) - query_presum(r, t2);
		return p2 - p1;
	}
}
using namespace FenwickTree;

signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr); cout.tie(nullptr);
	cin >> n;
	for (int i = 1; i <= n; ++i) {
		int x; cin >> x;
		int pos = (i + 1) >> 1;
		if (i & 1) add(pos, pos, x, ta1, ta2);
		else add(pos, pos, x, tb1, tb2);
	}
	int q; cin >> q;
	for (int i = 1; i <= q; ++i) {
		int l, r, v;
		cin >> l >> r >> v;
		int la = ((l + 1) >> 1) + !(l & 1), ra = (r + 1) >> 1;
		int lb = (l >> 1) + (l & 1), rb = r >> 1;
		if (la <= ra) add(la, ra, v, ta1, ta2);
		if (lb <= rb) add(lb, rb, v, tb1, tb2);
		int ans = query_sum(1, n, ta1, ta2) - query_sum(1, n, tb1, tb2);
		if (n & 1) cout << ans << '\n';
		else cout << -ans << '\n';
	}
}

B. Bonuses on a Line

题意:数轴上有 \(n\) 个点,坐标为 \((x_i,0)\) ,你从原点开始能够移动 \(t\) 个单位距离,询问最多能够经过几个点。

分析:将正点和负点分开存,然后枚举我们到达的左端点(右端点同理),二分搜索从该端点向右走最多能够经过几个正点。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
 
int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr); cout.tie(nullptr);
	ll n, t; cin >> n >> t;
	vector<ll> pos, neg;
	for (int i = 0; i < n; ++i) {
		ll x; cin >> x;
		if (x < 0) neg.emplace_back(-x);
		else pos.emplace_back(x);
	}
	sort(neg.begin(), neg.end());
	sort(pos.begin(), pos.end());
	ll ans = 0;
	for (int i = 0; i < neg.size(); ++i) {
		if (neg[i] > t) break;
		ll maxd = t - neg[i] - neg[i];
		ll res = 0;
		if (maxd > 0) res = upper_bound(pos.begin(), pos.end(), maxd) - pos.begin();
		ans = max(ans, i + res + 1);
	}
	for (int i = 0; i < pos.size(); ++i) {
		if (pos[i] > t) break;
		ll maxd = t - pos[i] - pos[i];
		ll res = 0;
		if (maxd > 0) res = upper_bound(neg.begin(), neg.end(), maxd) - neg.begin();
		ans = max(ans, i + res + 1);
	}
	cout << ans;
}

C. Manhattan Distance

题意:给定二维平面上 \(n\) 个点,求曼哈顿距离第 \(k\) 近的点对。

分析:二分搜索答案,计数时有一个技巧:因为距离某个点 \((x,y)\) 的曼哈顿距离不超过 \(d\) 的点可以看作在一个旋转了 \(90°\) 的正方形中,该正方形顶点分别为 \((x-d,y);(x,y-d);(x+d,y);(x,y+d)\) ,处理较为复杂;因此,我们不妨直接将坐标系旋转 \(90°\) ,那样所有距离某个点 \((x,y)\) 的曼哈顿距离不超过 \(d\) 的点都可以看作在一个顶点分别为 \((x-\frac{d}{2},y-\frac{d}{2});(x-\frac{d}{2},y+\frac{d}{2});(x+\frac{d}{2},y+\frac{d}{2});(x+\frac{d}{2},y-\frac{d}{2})\) 的正方形中,我们可以利用树状数组快速计数。时间复杂度 \(O(n\log n \log 4e8)\)

#include <bits/stdc++.h>
#include <random>
#define ll long long
#define mp make_pair
#define SIZE 100010
using namespace std;
ll n, k;

namespace FenwickTree {
	int t[SIZE];

	int lowbit(int x) { return x & (-x); }

	void add(int pos, int x, int t[]) { // 单点修改
		for (; pos <= n; pos += lowbit(pos)) {
			t[pos] += x;
		}
	}

	int query_presum(int pos, int t[]) { // 单点查询
		int ans = 0;
		for (; pos > 0; pos -= lowbit(pos)) {
			ans += t[pos];
		}
		return ans;
	}
}
using namespace FenwickTree;

ll check(int d, vector<pair<int, int> >& p, vector<int>& y) {
	ll res = 0;
	memset(t, 0, sizeof(t));
	for (int i = 0, j = 0; i < n; ++i) {
		while (j < i && p[i].first - p[j].first > d) {
			add(lower_bound(y.begin(), y.end(), p[j].second) - y.begin() + 1, -1, t);
			j++;
		}
		res += query_presum(upper_bound(y.begin(), y.end(), p[i].second + d) - y.begin(), t);
		res -= query_presum(lower_bound(y.begin(), y.end(), p[i].second - d) - y.begin(), t);
		add(lower_bound(y.begin(), y.end(), p[i].second) - y.begin() + 1, 1, t);
	}
	return res;
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr); cout.tie(nullptr);
	cin >> n >> k;
	vector<pair<int, int> > p(n);
	vector<int> y;
	for (int i = 0; i < n; ++i) {
		int a, b; cin >> a >> b;
		p[i].first = a + b;
		p[i].second = a - b;
		y.emplace_back(p[i].second);
	}
	sort(p.begin(), p.end());
	sort(y.begin(), y.end());
	y.erase(unique(y.begin(), y.end()), y.end());
	
	int L = 0, R = 4e8, mid;
	while (R > L) {
		mid = (L + R) >> 1;
		if (check(mid, p, y) < k) L = mid + 1;
		else R = mid;
	}
	cout << L;
}

D. Lexicographically Minimal Shortest Path

题意:给定一个 \(n\) 个点 \(m\) 条边的无向无权图,每条边都有一个字母,要求找出该图中从节点 \(1\) 到节点 \(n\) 的字典序最小的最短路。

分析:我们先将所有点距离点 \(n\) 的距离预处理出来,由于无权边,因此一个 \(O(m)\)\(BFS\) 就能解决。随后,从节点 \(1\) 出发,搜索所有后继节点,并找到所有后继节点中距离点 \(n\) 的距离为前驱节点到点 \(n\) 距离减一的节点中字典序最小的那个(如果有多个就全部丢进 \(vector\) ),然后重复这一操作直到找到字典序最小的最短路。

#include <bits/stdc++.h>
#define ll long long
#define mp make_pair
#define SIZE 200010
using namespace std;
vector<pair<int, char> > vec[SIZE];

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr); cout.tie(nullptr);
	int n, m; cin >> n >> m;
	for (int i = 0; i < m; ++i) {
		int a, b; char c;
		cin >> a >> b >> c;
		--a, --b;
		vec[a].emplace_back(mp(b, c));
		vec[b].emplace_back(mp(a, c));
	}

	vector<int> dis(n, -1);
	queue<int> q;
	q.push(n - 1);
	dis[n - 1] = 0;
	while (!q.empty()) {
		int top = q.front();
		q.pop();
		for (auto i : vec[top]) {
			int to = i.first;
			if (dis[to] == -1) {
				dis[to] = dis[top] + 1;
				q.push(to);
			}
		}
	}

	string ans = "";
	vector<int> pa(n, -1);
	vector<int> tmp;
	tmp.emplace_back(0);
	for (int i = 0; i < dis[0]; ++i) {
		char minc = 'z' + 1;
		vector<int> nxt;
		for (auto j : tmp) {
			for (auto k : vec[j]) {
				if (dis[k.first] == dis[j] - 1) {
					if (k.second < minc) {
						pa[k.first] = j;
						minc = k.second;
						nxt = { k.first };
					}
					else if (k.second == minc) {
						pa[k.first] = j;
						nxt.emplace_back(k.first);
					}
				}
			}
		}
		sort(nxt.begin(), nxt.end());
		nxt.erase(unique(nxt.begin(), nxt.end()), nxt.end());
		tmp = nxt;
		ans += minc;
	}

	vector<int> path;
	int fa = n - 1;
	while (fa != -1) {
		path.emplace_back(fa + 1);
		fa = pa[fa];
	}
	reverse(path.begin(), path.end());
	cout << path.size() - 1 << '\n';
	for (auto i : path) cout << i << ' ';
	cout << '\n' << ans;
}

E. Fluctuations of Mana

题意:略。

分析:前缀和求最值。

#include <bits/stdc++.h>
#define ll long long
using namespace std;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr); cout.tie(nullptr);
	ll n, ans = 1e18;
	cin >> n;
	vector<ll> a(n);
	vector<ll> pre(n);
	for (auto& i : a) cin >> i;
	for (int i = 0; i < n; ++i) pre[i] = (!i ? a[i] : a[i] + pre[i - 1]);
	for (auto i : pre) ans = min(ans, i);
	cout << (ans >= 0 ? 0 : -ans);
}

F. Moving Target

题意:有一排编号分别为 \(1,2,\cdots,n\) 的窗户,某个窗户后面有一个目标物,你每次可以打开一个窗户来寻找这个目标物,如果找到就结束了,如果没找到就要继续找并且该目标物会转移到右边的窗户(即从 \(k\) 变成 \(k+1\) ),请你给出一种寻找次数最少的构造,必须保证一定能够找到这个目标物。

分析:其实就是构造一个 1 3 5 ... 的数列,注意特判奇偶。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
 
int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr); cout.tie(nullptr);
	int n; cin >> n;
	cout << ((n & 1) ? (n + 1) / 2 : n / 2 + 1);
	cout << '\n';
	for (int i = 0; i < (n + 1) / 2; ++i) cout << 2 * i + 1 << ' ';
	if (!(n & 1)) cout << n;
}

G. Nuts and Bolts

题意:交互题。有 \(n\) 个尺寸为 \(1,2,\cdots,n\) 的螺栓和螺母,现在他们的顺序被打乱了,你需要将他们两两配对(大小相同的),最多能够询问 \(5n\log n\) 次。每次询问中,你可以询问两个下标 \(i\)\(j\) 表示询问第 \(i\) 个螺栓和第 \(j\) 个螺母的大小关系,如果螺栓的尺寸大于螺母将返回 \(>\) ,若等于返回 \(=\) ,若小于返回 \(<\)

分析:肯定是分治的思想,我们每次随机一个下标 \(x\) ,通过两次 \(O(n)\) 的询问将数组 \(a\) 中小于 \(a[x]\) 的元素和大于 \(a[x]\) 的元素分别丢进两个数组,将数组 \(b\) 中小于 \(a[x]\) 的元素和大于 \(a[x]\) 的元素分别丢进两个数组,并且找到数组 \(b\) 中与 \(a[x]\) 相等的元素的下标。然后将小于 \(a[x]\) 的两个数组和大于 \(a[x]\) 的两个数组分成两组进行递归搜索,理论复杂度 \(O(n\log n)\)

#include <bits/stdc++.h>
#include <random>
using namespace std;
vector<int> ans;

void solve(vector<int>& A, vector<int>& B) {
	if (A.empty()) return;
	default_random_engine seed(time(0));
	uniform_int_distribution<int> gen(0, A.size() - 1);
	int x = A[gen(seed)];
	int pos = -1;
	vector<int> AL, AR, BL, BR;
	for (auto i : B) {
		cout << "? " << x + 1 << " " << i + 1 << endl;
		char ch; cin >> ch;
		if (ch == '<') BR.emplace_back(i);
		else if (ch == '>') BL.emplace_back(i);
		else pos = i;
	}
	ans[x + 1] = pos + 1;
	for (auto i : A) {
		cout << "? " << i + 1 << " " << pos + 1 << endl;
		char ch; cin >> ch;
		if (ch == '<') AL.emplace_back(i);
		else if (ch == '>') AR.emplace_back(i);
	}
	solve(AL, BL);
	solve(AR, BR);
}

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr); cout.tie(nullptr);
	int n; cin >> n;
	ans.resize(n + 1, -1);
	vector<int> A(n), B(n);
	for (int i = 0; i < n; ++i) A[i] = B[i] = i;
	solve(A, B);
	cout << "!";
	for (int i = 1; i <= n; ++i) cout << " " << ans[i];
}

H. Tree Painting

题意:给定一棵 \(n\) 个点的树,现在每次操作能够选择树上的两个节点,然后将这两个节点之间的边和点全部染色,询问将整棵树的边和点完全染色需要几次操作。

分析:最优策略必定是每次选择两个叶子节点,然后将这两个节点间的路径染色,因此结果为 \(\frac{叶子节点数+1}{2}\)

#include <bits/stdc++.h>
#define ll long long
using namespace std;
 
int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr); cout.tie(nullptr);
	int n; cin >> n;
	vector<int> deg(n + 1);
	for (int i = 1; i < n; ++i) {
		int u, v; cin >> u >> v;
		deg[u]++; deg[v]++;
	}
	int leaves = 0;
	for (int i = 1; i <= n; ++i)
		if (deg[i] == 1)
			++leaves;
	cout << (leaves + 1) / 2;
}

I. Sorting Colored Array

题意:给定一个数组,第 \(i\) 个位置的元素有一个数值 \(a_i\) 和一个颜色 \(c_i\) ,不同颜色的相邻元素可以交换位置,询问能否将数组交换为按照数值大小从小到大排列的。

分析:因为相同颜色的元素不能交换,因此我们将元素按照颜色分类存储,如果某种颜色的颜色中存在逆序对,那么就不能,否则就能。

#include <bits/stdc++.h>
#define SIZE 1000010
using namespace std;
 
bool check(vector<int>& a, vector<int>& b) {
	for (int i = 0; i < a.size(); ++i)
		if (a[i] != b[i])
			return true;
	return false;
}
 
int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr); cout.tie(nullptr);
	int n; cin >> n;
	map<int, vector<int> > MP;
	for (int i = 1; i <= n; ++i) {
		int x, y; cin >> x >> y;
		MP[y].emplace_back(x);
	}
	for (auto it : MP) {
		vector<int> a, b;
		a = b = it.second;
		sort(a.begin(), a.end());
		if (check(a, b)) {
			cout << "NO";
			return 0;
		}
	}
	cout << "YES";
}

J. The Battle of Mages

题意:输出答案题。有两个能随机召唤生物的法师,每个召唤出的生物有一个属性:攻击力。每个法师能从他的召唤池中召唤 \(k\) 只不同的生物,召唤出的生物攻击力之和更高的获胜。现在要求你构造出这两个法师的召唤池,使得 \(k=1,3\) 时,第一个法师胜率更高, \(k=2\) 时第二个法师胜率更高。

分析:略。

#include <bits/stdc++.h>
#define ll long long
using namespace std;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr); cout.tie(nullptr);
	cout << "3\n2 7 7\n3\n5 5 5";
}

K. Table

题意:给定一个桌子的四个桌脚的长度,判断这四个桌脚能否恰好保证支撑起这个桌子(即保证桌面是一个平面)。

分析:如图:

设这四根桌脚的长度分别为 \(a_0,a_1,a_2,a_3\) (从小到大排列),如果 \(ABCD\) 为一平面,则必须满足:

\(|DM|=|CN|\Leftrightarrow |CC'|=|CN|+|C'N|=|BB'|+|DM|\Leftrightarrow a_3=a_1+a_2-a_0\)

#include <bits/stdc++.h>
#define ll long long
using namespace std;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr); cout.tie(nullptr);
	vector<int> a(4);
	for (auto& i : a) cin >> i;
	sort(a.begin(), a.end());
	if (a[3] == a[2] + a[1] - a[0]) cout << "YES";
	else cout << "NO";
}

L. The Dragon Land

题意:一个勇者需要穿越一片龙的领地,一共有 \(n\) 只龙,每只龙有一个赏金 \(a_i\) ,勇者每攻击一只龙都需要花钱修一次装备,第一次修花费 \(1\) ,每次修复的价格增加 \(1\) ,询问勇者最多能赚多少钱。

分析:排序一下贪心。

#include <bits/stdc++.h>
#define ll long long
using namespace std;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr); cout.tie(nullptr);
	ll n; cin >> n;
	vector<ll> a(n);
	for (auto& i : a) cin >> i;
	sort(a.begin(), a.end());
	reverse(a.begin(), a.end());
	ll ans = 0, cost = 1;
	for (auto i : a) {
		if (i > cost) {
			ans += i - cost;
			++cost;
		}
	}
	cout << ans;
}

M. Notifications

题意:略。

分析:简单模拟。

#include <bits/stdc++.h>
#define ll long long
using namespace std;

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr); cout.tie(nullptr);
	int t; cin >> t;
	vector<pair<ll, ll> > a(t);
	for (auto& i : a) cin >> i.first >> i.second;
	sort(a.begin(), a.end());
	ll ans = a[0].first + a[0].second;
	for (int i = 1; i < t; ++i) {
		if (a[i].first <= ans) ans += a[i].second;
		else ans = a[i].first + a[i].second;
	}
	cout << ans;
}
posted @ 2020-04-22 14:46  st1vdy  阅读(2140)  评论(0编辑  收藏  举报