2019-2020 ICPC Northwestern European Regional Programming Contest (NWERC 2019) 部分題解


2019-2020 ICPC Northwestern European Regional Programming Contest (NWERC 2019)

A. Average Rank

題意\(n\) 個參賽選手將開展 \(w\) 個星期的比賽,每個星期的比賽都會有一些選手加一分,選手按照分數從大到小排名(分數相等並列)。計算每個選手所有星期排名的平均值。

分析:考慮一下當某個人加了一分后,他的名次將發生什么變化,假設他當前分數為 \(x\) ,則 \(x\) 分數段的人數減一, \(x+1\) 分數段人數加一,因此他的名次將會提升至分數為 \(x+1\) 這檔的排名,這一輪中分數仍為 \(x\) 的人的排名加一。於是就得出了 \(O(nw)\) 的模擬,但是這樣的復雜度是不能接受的。對於每一輪的更新,我們實際上不需要對每一個人的排名進行更新,只需要對有分數變化的人進行操作,不變化的人只有到了他發生變化時再更新(類似於線段樹 \(lazy\) 標記)。給每個人打一個標記,標記他上次更新前的分數;每個分數段也打一個標記,標記這個分數上次更新的時間;然后就能 \(O(n)\) 更新了。

#define _CRT_SECURE_NO_WARNINGS
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma comment(linker, "/stack:200000000")
#include <bits/stdc++.h>
#define SIZE 300010
#define rep(i, a, b) for (int i = a; i <= b; ++i)
#define mp make_pair
#define ll long long
using namespace std;
void io() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); }
ll n, w, k;
ll num[SIZE], p[SIZE], rk[SIZE], lazy[SIZE], pre[SIZE];
double sum[SIZE];

int main() {
	io(); cin >> n >> w;
	rep(i, 0, (w - 1)) {
		cin >> k;
		rep(j, 0, (k - 1)) {
			int x; cin >> x;
			num[p[x]] += rk[p[x]] * (i - lazy[p[x]]);
			lazy[p[x]] = i;
			++rk[p[x]];
			sum[x] += num[p[x]] - pre[x];

			++p[x];
			num[p[x]] += rk[p[x]] * (i - lazy[p[x]]);
			lazy[p[x]] = i;
			pre[x] = num[p[x]];
		}
	}
	rep(i, 1, n) {
		num[p[i]] += rk[p[i]] * (w - lazy[p[i]]);
		lazy[p[i]] = w;
		sum[i] += num[p[i]] - pre[i];
	}
	rep(i, 1, n) cout << fixed << setprecision(9) << (1.0 + sum[i] / w) << '\n';
}

C. Canvas Line

題意:一條直線上掛着很多油畫,每幅油畫覆蓋了一段區間 \([l_i,r_i]\) ,任意兩幅油畫沒有重疊部分。為了掛起一幅油畫,我們需要用兩個架子夾住油畫,但不能用多於兩個夾子夾住同一幅油畫。已知有 \(p\) 個位置 \(x_i\) 上已經存在夾子了,現在你需要給出一種使用夾子數量最少的構造來掛起所有油畫,或者判斷不存在這種構造。

分析:很明顯的貪心,我們盡可能夾住兩幅油畫的相交處,因此我們先遍歷一遍油畫的相交點,注意判斷合法性,最后再給每幅不滿足要求的油畫添上夾子。構造不存在當且僅當一開始就有某幅油畫上存在多於兩個夾子。

#define _CRT_SECURE_NO_WARNINGS
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma comment(linker, "/stack:200000000")
#include <bits/stdc++.h>
#define SIZE 200005
#define rep(i, a, b) for (int i = a; i <= b; ++i)
#define mp make_pair
#define ll long long
using namespace std;
void io() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); }
int n, m, ans;
struct Seg {
	int x, y;
	Seg() {}
	Seg(int x_, int y_) : x(x_), y(y_) {}
	bool operator < (const Seg& b) const {
		return x < b.x;
	}
}p[SIZE];
int MP[SIZE];
map<int, int> vis;

int main() {
	io(); cin >> n;
	rep(i, 1, n) cin >> p[i].x >> p[i].y;// , vis[p[i].x] = vis[p[i].y] = 0;
	sort(p + 1, p + 1 + n);
	cin >> m;
	rep(i, 1, m) {
		int x; cin >> x;
		int pos = upper_bound(p + 1, p + 1 + n, Seg(x, 0)) - p - 1;
		if (p[pos].y < x) continue;
		vis[x]++, MP[pos]++;
		if (p[pos - 1].y == x) MP[pos - 1]++;
	}
	rep(i, 1, n) {
		if (MP[i] > 2) {
			cout << "impossible";
			return 0;
		}
	}
	vector<int> res;
	rep(i, 2, n) {
		if (p[i - 1].y == p[i].x) {
			if (MP[i - 1] >= 2 || MP[i] >= 2 || vis[p[i].x]) continue;
			++ans;
			res.push_back(p[i].x);
			vis[p[i].x] = 1;
			MP[i - 1]++, MP[i]++;
		}
	}
	rep(i, 1, n) {
		if (MP[i] >= 2) continue;
		else {
			int tot = 2 - MP[i], now = p[i].x + 1;
			while (tot) {
				if (vis[now]) { ++now; continue; }
				vis[now] = 1;
				res.push_back(now++);
				--tot;
			}
		}
	}
	cout << res.size() << '\n';
	for (auto i : res) cout << i << ' ';
}

D. Disposable Switches

題意:給定一個 \(n\) 個點,\(m\) 條邊的無向圖,每條邊的邊權是 \(\frac{l_i}{u}+c\)(其中 \(u,c\) 是兩個任意正數,\(l_i\) 給定)。詢問當 \(u,c\) 任意取值時,哪些邊不可能在最短路上。

分析:由於 \(u,c>0\) 我們將所有邊邊權 \(\times u\) 。然后考慮:如果一條最短路經過了恰好 \(k\) 條邊,那么其權值可以表示為 \(\sum^k_{i=1}l'_i +kuc\Leftrightarrow minl_k + kx\) ,其中 \(minl_k\) 代表用 \(l_i\) 作為邊權建圖時,恰好經過 \(k\) 條邊的最短路權值,\(x=uc\) 。這意味着,如果我們處理出所有的 \(minl_k\) ,那么我們就能將只取 \(k\) 條邊時的最短路權值用一個一次函數 \(y=kx+minl_k\) 表示。如果我們將所有這樣的一次函數畫出來,做一個半平面交,如下圖:

所有能暴露在下方的邊(圖中虛線部分)都是可以通過合適的取值,成為最短路的。因此,剩下的問題就是如何求出所有的 \(minl_k\)

這一過程可以通過一個類似於 \(Floyd\)\(dp\) 推出,可參考代碼。

#define _CRT_SECURE_NO_WARNINGS
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma comment(linker, "/stack:200000000")
#include <bits/stdc++.h>
#define rep(i, a, b) for (long long i = a; i <= b; ++i)
#define mp make_pair
#define SIZE 2010
#define ll long long
using namespace std;
void io() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); }
typedef pair<ll, ll> pll;
const ll INF = 1e18;
int n, m;
vector<pll> vec[SIZE];
ll dis[SIZE][SIZE];
bool res[SIZE], vis[SIZE][SIZE];

bool cross(pll a, pll b, pll c) {
	long double xa = c.second - a.second, ya = a.first - c.first,
		xb = b.second - a.second, yb = a.first - b.first;
	return xa * yb < xb * ya;
}

void Floyd() {
	for (int i = 0; i <= n; ++i) {
		for (int j = 0; j <= n; ++j) {
			dis[i][j] = INF;
		}
	}
	dis[0][1] = 0;
	for (int i = 0; i <= n - 2; ++i) {
		for (int j = 1; j <= n; ++j) {
			for (auto k : vec[j]) {
				dis[i + 1][k.first] = min(dis[i + 1][k.first], dis[i][j] + k.second);
			}
		}
	}
	/*rep(i, 1, n) {
		rep(j, 1, n) {
			cout << dis[i][j] << ' ';
		}
		cout << '\n';
	}*/
}

void dfs(int now, int to) {
	res[to] = vis[now][to] = true;
	if (!now) return;
	for (auto i : vec[to]) {
		if (dis[now - 1][i.first] + i.second == dis[now][to]) {
			if (vis[now - 1][i.first]) continue;
			dfs(now - 1, i.first);
		}
	}
}

int main() {
	io(); cin >> n >> m;
	rep(i, 1, m) {
		int u, v, c;
		cin >> u >> v >> c;
		vec[u].emplace_back(mp(v, c));
		vec[v].emplace_back(mp(u, c));
	}
	Floyd();

	vector<pll> stk;
	for (int i = n - 1; i >= 1; --i) {
		pll p = mp(i, dis[i][n]);
		//cout << p.first << ' ' << p.second << '\n';
		while (stk.size() >= 2) {
			if (cross(stk[stk.size() - 2], stk[stk.size() - 1], p)) stk.pop_back();
			else break;
		}
		if (stk.size() == 1 && stk.back().second > p.second) stk.pop_back();
		stk.emplace_back(p);
	}
	//cout << '\n';
	//for (auto i : stk) cout << i.first << ' ' << i.second << '\n';
	for (auto i : stk) dfs(i.first, n);

	vector<int> ans;
	rep(i, 1, n) {
		if (res[i]) continue;
		ans.emplace_back(i);
	}
	cout << ans.size() << '\n';
	for (auto i : ans) cout << i << ' ';
}

E. Expeditious Cubing

題意:略。

分析:簡單分類討論,不過需要注意精度,轉化成整形再進行運算。

#define _CRT_SECURE_NO_WARNINGS
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma comment(linker, "/stack:200000000")
#include <bits/stdc++.h>
#define SIZE 200010
#define rep(i, a, b) for (int i = a; i <= b; ++i)
#define mp make_pair
#define ll long long
using namespace std;
void io() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); }
int n, t, cnt;
double a[10], T, inf = 2100;
int b[10];

int main() {
	io(); rep(i, 1, 4) cin >> a[i], b[i] = round(a[i] * 100.0);
	cin >> T; t = round(T * 300.0);
	sort(b + 1, b + 1 + 5);
	int tmp = 0;
	rep(i, 2, 4) tmp += b[i];
	if (tmp > t) { cout << "impossible"; return 0; }

	b[1] = inf;
	sort(b + 1, b + 1 + 5);
	tmp = 0;
	rep(i, 2, 4) tmp += b[i];
	if (tmp <= t) { cout << "infinite"; return 0; }

	tmp = 0;
	rep(i, 2, 3) tmp += b[i];
	cout << fixed << setprecision(2) << (t - tmp) / 100.0;
}

F. Firetrucks Are Red

題意:給定 \(n\) 個人,每個人有一個數字集合,我們稱兩個人是有關系的當這兩個人擁有至少一個相同的數字,現在請你證明這 \(n\) 個人之間是否全部直接或者間接地存在關系,如果存在給出一種構造。

分析:其實就是按照數字關系建圖找最小生成樹,直接跑並查集就可以解決了。

#define _CRT_SECURE_NO_WARNINGS
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma comment(linker, "/stack:200000000")
#include <bits/stdc++.h>
#define SIZE 200010
#define rep(i, a, b) for (int i = a; i <= b; ++i)
#define mp make_pair
#define ll long long
using namespace std;
void io() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); }
ll n, t, cnt, a[SIZE];
struct UnionSet { //並查集
	int pa[SIZE];
	void init(int n) {
		for (int i = 1; i <= n; i++) pa[i] = i;
	}
	int find(int x) {
		return pa[x] == x ? x : pa[x] = find(pa[x]);
	}
	bool IsSame(int x, int y) { return find(x) == find(y); }
	int union_vertices(int x, int y) {
		int xr = find(x), yr = find(y);
		if (xr != yr) {
			pa[yr] = xr;
			return 1;
		}
		return 0;
	}
} U;

map<int, int> MP;
vector<tuple<int, int, int> > ans;

int main() {
	io(); cin >> t;
	U.init(200000);
	rep(ii, 1, t) {
		cin >> n;
		rep(i, 1, n) {
			int x; cin >> x;
			if (MP.count(x)) {
				if (U.IsSame(MP[x], ii)) continue;
				else U.union_vertices(MP[x], ii), ans.emplace_back(tuple<int, int, int>(MP[x], ii, x));
			}
			else MP[x] = ii;
		}
	}
	if (ans.size() < t - 1) { cout << "impossible"; return 0; }
	for (auto i : ans) cout << get<0>(i) << ' ' << get<1>(i) << ' ' << get<2>(i) << '\n';
}

G. Gnoll Hypothesis

題意:怪物池中有 \(n\) 只怪物,每只怪物的生成概率為 \(p_i\) 。現在怪物池中的怪物只有 \(k\) 只會生成(任意 \(k\) 只),不生成的怪物原本的生成概率將會向后轉移給下一只會可能生成的怪物(如果最后的怪物不會生成,那么概率會轉移給第一只可能生成的)。求所有怪物現在的生成概率。

分析:這題的思路有點像 Atcoder Dwango Programming Contest 6th 里的 B 題,核心思想都是如何處理將前面的怪物合並到后面。如果 \(p_i\) 需要合並到 \(p_j\) \((i<j)\) ,那么 \(p_k(i\leq k<j)\) 必定是不能生成的怪物,根據這一性質我們就能對每一位 \(p_i'\) 進行計算了:\(p_i'=\sum^{n-k}_{j=0}p_{i-j}\cdot\frac{C^{k-1}_{n-j-1}}{C^{k}_{n}}\)

#define _CRT_SECURE_NO_WARNINGS
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma comment(linker, "/stack:200000000")
#include <bits/stdc++.h>
#define SIZE 505
#define rep(i, a, b) for (int i = a; i <= b; ++i)
#define mp make_pair
#define ll long long
using namespace std;
void io() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); }
int n, k, cnt;
long double dp[SIZE][SIZE];
void init(int n) { //dp[n][m] 即為 nCm
	rep(i, 0, n) dp[i][0] = dp[i][i] = 1;
	rep(i, 1, n) {
		rep(j, 1, i) {
			dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1];
		}
	}
}

int main() {
	cin >> n >> k;
	init(n);
	vector<long double> a(n + n), b(n + n), ans(n);
	rep(i, 0, (n - 1)) cin >> a[i], a[i + n] = a[i];
	rep(i, (n - k), (n + n - k - 1)) {
		rep(j, 0, (n - k)) b[i] += a[i - j] * dp[n - j - 1][k - 1] / dp[n][k];
		ans[i % n] = b[i];
	}
	rep(i, 0, (n - 1)) printf("%.12Lf ", ans[i]);
}

H. Height Profile

題意:給出一個數列 \(h_n\)\(h_i\) 表示第 \(i\) \(km\) 處的海拔高度,相鄰兩個位置之間的道路用線段表示。給出 \(k\) 個詢問,每個詢問給出一個坡度 \(g\) ,要求你找出坡度大於等於 \(g\) 的道路最大長度,或者判斷不存在這樣的道路。

分析:注意到 \(k\leq 50\) ,因此可以是 \(O(knlogn)\) 的一個算法。我們可以將這個高度序列看作是一個一次的分段函數,直接對於整個分段函數全部減去函數 \(y=gx\) ,那么我們只需要找到滿足 \(y_r-y_l\geq 0\)\(|r-l|_{max}\) ,這個過程只需要對序列排序后就能快速查找了。但是這樣還會漏掉點落在線段上的一些特判,我們用差值比例計算一下多余部分就行了。

#define _CRT_SECURE_NO_WARNINGS
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma comment(linker, "/stack:200000000")
#include <bits/stdc++.h>
#define SIZE 200010
#define rep(i, a, b) for (int i = a; i <= b; ++i)
#define mp make_pair
#define ll long long
using namespace std;
void io() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); }
int n, k, maxs = -2e9;
int h[SIZE], p[SIZE];

double sol(int dy) {
	double ans = -1.0;
	vector<pair<int, int> > vec(n + 1), tmp(n + 1);
	rep(i, 0, n) vec[i] = mp(h[i] - i * dy, i), tmp[i] = vec[i];
	sort(vec.begin(), vec.end());
	int L = n, R;
	for (auto& it : vec) {
		R = it.second;
		if (L < R) {
			double res = 0;
			if (L > 0) res = max(res, (double)(tmp[R].first - tmp[L].first) / fabs(tmp[L].first - tmp[L - 1].first));
			if (R < n) res = max(res, (double)(tmp[R].first - tmp[L].first) / fabs(tmp[R + 1].first - tmp[R].first));
			if (res > 1.0) res = 1.0;
			ans = max(ans, R - L + res);
		}
		L = min(L, R);
	}
	return ans;
}

int main() {
	io(); cin >> n >> k;
	rep(i, 0, n) cin >> h[i];
	rep(i, 1, n) maxs = max(maxs, h[i] - h[i - 1]);
	rep(i, 1, k) {
		double g; cin >> g;
		ll dy = round(10.0 * g);
		if (maxs < dy) { cout << "-1\n"; continue; }
		cout << fixed << setprecision(12) << sol(dy) << '\n';
	}
}

I. Inverted Deck

題意:給定一個序列 \(a_n\) ,判斷能否翻轉其中一個區間使得整個序列變成單調不減的。

分析:我們直接 \(sort\) 一遍,然后遍歷判斷是否存在這一個區間就好了。

#define _CRT_SECURE_NO_WARNINGS
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#pragma comment(linker, "/stack:200000000")
#include <bits/stdc++.h>
#define SIZE 1000005
#define rep(i, a, b) for (int i = a; i <= b; ++i)
#define mp make_pair
#define ll long long
#define mod 1000000007
using namespace std;
void io() { ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr); }
ll n, t, m, a[SIZE];
 
int main() {
	io(); cin >> n;
	vector<int> b(n + 1);
	rep(i, 1, n) cin >> a[i], b[i] = a[i];
	sort(a + 1, a + 1 + n);
	vector<int> dif;
	rep(i, 1, n) {
		if (a[i] == b[i]) continue;
		else dif.emplace_back(i);
	}
	if (dif.size() == 0) { cout << "1 1\n"; return 0; }
	reverse(b.begin() + *dif.begin(), b.begin() + *dif.rbegin() + 1);
	bool f = true;
	rep(i, 1, n) {
		if (a[i] != b[i]) {
			f = false;
			break;
		}
	}
	if (f) cout  << *dif.begin() << ' ' << *dif.rbegin() << '\n';
	else cout << "impossible\n";
}

J. Jackdaws And Crows

題意:給定一個長度為 \(n\) 的數組 \(a\) 和兩個參數 \(r,c\) ,你可以使用 \(r\) 秒時間刪除數組中的一個數字,或者使用 \(c\) 秒時間將數組中的任意個數字加一或減一。詢問將數組變成相鄰異號的數組所需的最少時間。

分析:待補。


免責聲明!

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



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