比赛链接:

https://vjudge.net/contest/508283

C - Slipper

题意:

给定 \(n\) 个节点的树,已知 \(n - 1\) 条边及权重,有一个魔法,对于两个节点 \(u\)\(v\),当两点的深度差为 \(k\) 时,可以花费 \(p\) 的代价互相到达,问从 \(s\)\(t\) 的最小花费为多少。

思路:

直接建立深度差为 \(k\) 的节点相互之间的边会超时,考虑在两个深度之间建立两个虚点。一个虚点用来向深的地方转移,一个用来向浅的地方转移。

先建立虚点指向真实的节点的边,然后建立真实节点指向 + \(k\) 或者 - \(k\) 的虚点的边。然后跑一下 \(dijkstra\) 就行。

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const LL INF = 1e18 + 10;
void solve(){
	LL n;
	cin >> n;
	vector< vector< pair<LL, LL> > > e(3 * n + 1);
	for (int i = 0; i < n - 1; i ++ ){
		LL u, v, w;
		cin >> u >> v >> w;
		e[u].push_back({w, v});
		e[v].push_back({w, u});
	}
	vector<LL> dep(n + 1);
	LL maxDep = 0;
	function<void(LL, LL, LL)> dfsDep = [&](LL u, LL fa, LL depth){
		dep[u] = depth;
		maxDep = max(maxDep, depth);
		for (auto t : e[u]){
			LL w = t.first, v = t.second;
			if (v == fa) continue;
			dfsDep(v, u, depth + 1);
		}
	};
	dfsDep(1, 0, 1);
	LL k, p;
	cin >> k >> p;
	for (int i = 1; i <= n; i ++ ){
		if (dep[i] != 1)
			e[n + dep[i]].push_back({0, i});
		if (dep[i] != maxDep)
			e[2 * n + dep[i]].push_back({0, i});
		if (dep[i] - k >= 1)
			e[i].push_back({p, 2 * n + dep[i] - k});
		if (dep[i] + k <= maxDep)
			e[i].push_back({p, n + dep[i] + k});
	}
	LL s, t;
	cin >> s >> t;
	auto dijkstra = [&](){
		vector <LL> dis(3 * n + 1, INF);
		vector <bool> vis(3 * n + 1);
		dis[s] = 0;
		priority_queue< pair<LL, LL> , vector< pair<LL, LL> >, greater< pair<LL, LL> > > q;
		q.push({0, s});
		while(q.size()){
			auto t = q.top();
			q.pop();
			LL u = t.second;
			if (vis[u]) continue;
			vis[u] = 1;
			for (auto t : e[u]){
				LL w = t.first, v = t.second;
				if (dis[v] > dis[u] + w){
					dis[v] = dis[u] + w;
					q.push({dis[v], v});
				}
			}
		}
		cout << dis[t] << "\n";
	};
	dijkstra();
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	LL T = 1;
	cin >> T;
	while(T -- ){
		solve();
	}
	return 0;
}

F - BBQ

题意:

给定一个字符串,四个字母为一组,问将每组都变成 \(s_i = s_{i + 3} and s_{i + 1} = s_{i + 2}\) 的完美字符串的最少操作次数(空字符串也是满足条件的)。
每一步操作可以删除一个字母,添加一个字母,修改一个字母。

思路:

定义 \(dp[i]\) 表示将前 \(i\) 个字符串变成完美字符串的最少操作次数。
得到转移方程 \(dp[i] = dp[j] + cost(i + 1, j)\)
将长度 >= 8 的字符串变成一组合法的字符串是没有必要的。
因为,将它变成两组合法组,首先删除剩余的字符的操作次数为 \(n - 8\),然后对于每组,最多花费 2 步操作去修改字符,总共 \(n - 4\) 步操作。
而变成一组合法组,首先要删除剩余字符,总共 \(n - 4\) 步操作,总数已经大于转变为两组的操作次数了。
所以,只可能将长度 <= 7 的字符串变成一个合法组。
接着看 \(cost\) 操作。
长度为 1,直接删除,操作次数为 1。
长度为 2,直接删除两个或者添加两个,操作步数为 2。
长度为 3,如果其中有一对字符串了,添加一个就行,否则添加一个后还要修改一个。
长度为 4 及以上:
首先要删除 \(n - 4\) 个。
如果字符串中有 \(abba\) 型,删除其它字符即可。
如果有一对字符,且中间有两个以上的字符,删除后再修改一个,操作总数 \(n - 3\)
如果没有两个以上,例如 \(abcb\),也可以,修改任意两个就行,即当两个相同的字符不同时落在边界上,操作总数也为 \(n - 3\)
其它情况都是删除后修改两个,操作次数为 \(n - 2\)

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int INF = 0x3f3f3f3f, N = 1e6 + 10;
LL dp[N], L[N], R[N];
string s;
LL cost(LL st, LL ed){
	LL len = ed - st + 1;
	if (len == 1) return 1;
	else if (len == 2) return 2;
	else if (len == 3){
		if (s[st] == s[st + 1] || s[st] == s[st + 2] || s[st + 1] == s[st + 2]) return 1;
		return 2;
	}
	else{
		LL use = 2, last = 0;
		for (int i = 0; i < 26; i ++ )
			L[i] = R[i] = 0;
		for (int i = st; i <= ed; i ++ ){
			LL c = s[i] - 'a';
			if (R[c]){
				if (L[c] < last) use = 4;
				last = max(last, R[c]);
				if (L[c] + 2 < i || (R[c] != st && i != ed)) use = max(use, 3LL);
			}
			else{
				L[c] = i;
			}
			R[c] = i;
		}
		return len - use;
	}
}
void solve(){
	cin >> s;
	LL n = s.size();
	s = "+" + s;
	for (int i = 1; i <= n; i ++ )
		dp[i] = INF;
	dp[0] = 0;
	for (int i = 0; i < n; i ++ )
		for (int j = 1; j <= 7 && i + j <= n; j ++ )
			dp[i + j] = min(dp[i + j], dp[i] + cost(i + 1, i + j));
	cout << dp[n] << "\n";
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	LL T = 1;
	cin >> T;
	while(T -- ){
		solve();
	}
	return 0;
}

J - Bragging Dice

题意:

两个人玩吹牛游戏,每人杯子里有 \(n\) 个骰子,然后摇一次。
每回合可以吹牛,即说场上有 \(x(x >= 1)\)\(y\) 点数的骰子,假设前一个人说的是有 \(x1\)\(y1\) 点数的骰子,那么要满足以下两个条件之一:
1.\(x > x1\) and \(1 <= y <= 6\)
2.\(x = x1\) and \(y > y1\)
每回合可以选择挑战对方说的,如果场上确实有吹牛说的那么多点数的骰子,那么挑战者获胜,否则被挑战者获胜。
游戏有三个特殊规则:
1.如果在这之前没有说过有多少个 1 点的骰子,那么场上的 1 可以被当作任意其他点数。
2.如果一个人的杯子中的点数相同,那么则认为该点数的骰子多一个。
3.如果一个人的杯子中的点数全部不同,那么认为该被子里任何点数的骰子数量为 0。
现在已知双方手上的骰子的点数,且他们彼此知道对方的所有点数,问先手是否必胜。

思路:

除了一个特殊情况,其它都先手必胜,只需要选择数量最多且点数最大的即可。
因为 \(x >= 1\),当双方手上的点数全部不同时,即场上没有骰子,所以后手胜。

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
void solve(){
	LL n;
	cin >> n;
	vector <LL> cnta(7), cntb(7);
	LL x;
	for (int i = 0; i < n; i ++ ){
		cin >> x;
		cnta[x] ++ ;
	}
	for (int i = 0; i < n; i ++ ){
		cin >> x;
		cntb[x] ++ ;
	}
	bool diff1 = true, diff2 = true;
	for (int i = 1; i <= 6; i ++ ){
		if (cnta[i] > 1){
			diff1 = false;
			break;
		}
	}
	for (int i = 1; i <= 6; i ++ ){
		if (cntb[i] > 1){
			diff2 = false;
			break;
		}
	}
	cout << ((diff1 && diff2) ? "Just a game of chance." : "Win!") << "\n";
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	LL T = 1;
	cin >> T;
	while(T -- ){
		solve();
	}
	return 0;
}

L - Buy Figurines

题意:

\(n\) 个人排队买手办,\(m\) 个队伍,第 \(i\) 个人在 \(a_i\) 时间到达,花费 \(s_i\) 时间买手办,然后离开,第 \(i\) 个人来的时候选择人数最少中编号最小的队伍,问最后一个人离开的时间是多少。

思路:

通过线段树维护每一时刻第 \(i\) 个队伍中的人数,通过优先队列维护队伍中要走的人的信息。
每次新的人来的时候,先将要离开的人的信息更新了,然后查询人数最少中编号最小的队伍的 \(id\),更新线段树并将这个人离开的时间和下标加入优先队列中。

代码:

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 2e5 + 10;
struct SegmentTree{
	struct node{
		LL l, r, num;
	}tr[N * 4];
	void pushup(LL u){
		tr[u].num = min(tr[u << 1].num, tr[u << 1 | 1].num);
	}
	void build(LL u, LL l, LL r){
		if (l == r){
			tr[u] = {l, r, 0};
			return;
		}
		tr[u] = {l, r, 0};
		LL mid = l + r >> 1;
		build(u << 1, l, mid);
		build(u << 1 | 1, mid + 1, r);
		pushup(u);
	}
	void update(LL u, LL pos, LL k){
		if (tr[u].l == tr[u].r){
			tr[u].num += k;
		}
		else {
			LL mid = tr[u].l + tr[u].r >> 1;
			if (pos <= mid) update(u << 1, pos, k);
			if (pos > mid) update(u << 1 | 1, pos, k);
			pushup(u);
		}
	}
	LL query(LL u, LL pos){
		if (tr[u].l == tr[u].r) return tr[u].num;
		LL mid = tr[u].l + tr[u].r >> 1;
		if (pos <= mid) return query(u << 1, pos);
		else return query(u << 1 | 1, pos);
	}
	LL queryId(LL u){
		if (tr[u].l == tr[u].r) return tr[u].l;
		if (tr[u << 1].num <= tr[u << 1 | 1].num) return queryId(u << 1);
		else return queryId(u << 1 | 1);
	}
}segt;
void solve(){
	LL n, m;
	cin >> n >> m;
	vector < pair<LL, LL> > range(n);
	for (int i = 0; i < n; i ++ )
		cin >> range[i].first >> range[i].second;
	sort(range.begin(), range.end());
	vector <LL> last(m + 1);
	segt.build(1, 1, m);
	priority_queue < pair<LL, LL>, vector< pair<LL, LL> >, greater < pair<LL, LL> > > q;
	LL ans = 0;
	for (int i = 0; i < n; i ++ ){
		while(q.size() && q.top().first < range[i].first){
			LL pos = q.top().second;
			segt.update(1, pos, -1);
			q.pop();
		}
		LL id = segt.queryId(1);
		if (segt.query(1, id) == 0) last[id] = range[i].first + range[i].second;
		else last[id] += range[i].second;
		ans = max(ans, last[id]);
		q.push({last[id], id});
		segt.update(1, id, 1);
	}
	cout << ans << "\n";
}
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	LL T = 1;
	cin >> T;
	while(T -- ){
		solve();
	}
	return 0;
}
posted on 2022-08-14 13:05  Hamine  阅读(47)  评论(0)    收藏  举报