比赛链接:
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;
}