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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM