BalticOI 2022 D2T3 Boarding Passes 题解

First Post:

Last Update:

比较简单的套路题。

我们能决定的是组与组之间的顺序,以及每个人从前面登船还是从后面登船。也就是说,我们要找到一个最优的,这 个组的排列,而每个元素的贡献只与其前面的元素有关。

不难想到设 表示 的组已经登船的最小期望。考虑新加一个组 ,令 表示在第 组前登船的组集合为 的贡献。

考虑如何计算 。事实上,第 组中每个乘客对期望的贡献独立,可单独计算最后再加起来。我们先假设所有乘客都走前面上船,此时一个乘客 的贡献又可拆分为:“属于 中的组的在 之前的乘客数” 与 ”在第 组中的在 之前的乘客数的期望”之和。前者是确定的,好算,主要来看后者。

之前一共有 个属于第 组的座位,注意 是不随第 组内部登船顺序而改变的定值。那么,这 个乘客中,有 个在 之前上船的概率,其实就是在一个长度为 的随机均匀生成的排列中, 排在第 位的概率。容易发现这个概率不随 而变化,就是 。所以,在 之前的同组乘客数的期望就是

上文的分析基于乘客 走前面上船,实际上这个人走后面上船的贡献也可类似计算,两者取 之后加入 即可。

这里给出计算 的暴力代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
db Calc(int S, int x) {
db ans = 0;
int s1 = 0, s2 = 0;
int n1 = 0, n2 = 0;

for (int i = 1; i <= n; i++)
s1 += (S >> a[i]) & 1, s2 += a[i] == x;

for (int i = 1; i <= n; i++) {
if (a[i] == x)
ans += min(n1 + n2 / 2.0, (s1 - n1) + (s2 - n2 - 1) / 2.0);

n1 += (S >> a[i]) & 1;
n2 += a[i] == x;
}
return ans;
}

直接 计算 可以得到一个 的做法,拿到 分的高分。

优化 的计算也很简单,注意到第 组中,走前门上船的一定是一段前缀,反之亦然,所以可以二分出那个分界点,在分界点前的都走前面,分界点后的都走后面,贡献用前缀和统计一下即可。时间复杂度 。可以通过此题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5,M = 16,Sz = 1 << 15;
template<typename T> inline bool ckmin(T &x,const T &y) { return (x > y) && (x = y,true);}
typedef double db;
typedef long long ll;
int n,m;
int a[N],id[N]; // id : 在同组的第几个,从 0 开始
db f[Sz];
vector<int> Pos[N];
vector<ll> A[M][M],Sum[M][M];
db Calc(int S,int x) {
if(!Pos[x].size()) return 0;
auto calc1 = [&](const int &t) { // 传入其在组内第几个
assert(t >= 0);
db res = t / 2.0;
for(int i = 0;i < m;i++)
if((S >> i) & 1) res += A[i][x][t];
return res;
};
auto calc2 = [&](const int &t) {
assert(t >= 0);
db res = ((int)Pos[x].size() - t - 1) / 2.0;
for(int i = 0;i < m;i++)
if((S >> i) & 1) res += (int)Pos[i].size() - A[i][x][t];
return res;
};
int lef = -1,rig = (int)Pos[x].size() - 1;
while(lef < rig) {
int mid = lef + rig + 1 >> 1;
if(calc1(mid) <= calc2(mid)) lef = mid;
else rig = mid - 1;
}
int siz = Pos[x].size();
db c1 = 1ll * lef * (lef + 1) / 4.0;
if(lef >= 0) for(int i = 0;i < m;i++) if((S >> i) & 1) c1 += Sum[i][x][lef];
db c2 = 1ll * (siz - 1 - lef) * (siz - 2 - lef) / 4.0;
for(int i = 0;i < m;i++) if((S >> i) & 1) c2 += 1ll * (siz - 1 - lef) * Pos[i].size() - (Sum[i][x].back() - (lef >= 0 ? Sum[i][x][lef] : 0));
return c1 + c2;
}
int main() {
string str;
cin >> str;
n = str.size();
for(int i = 0;i < n;i++)
a[i + 1] = str[i] - 'A',m = max(m,a[i + 1] + 1);
for(int i = 1;i <= n;i++) {
id[i] = Pos[a[i]].size();
Pos[a[i]].push_back(i);
}
for(int i = 0;i < m;i++) {
int cnt = 0;
for(int j = 1;j <= n;j++)
if(a[j] == i) ++cnt;
else A[i][a[j]].push_back(cnt);
for(int j = 0;j < m;j++)
if(i != j && Pos[j].size()) {
Sum[i][j].resize(Pos[j].size());
Sum[i][j][0] = A[i][j][0];
for(int k = 1;k < Pos[j].size();k++)
Sum[i][j][k] = Sum[i][j][k - 1] + A[i][j][k];
}
}

for(int i = 0;i < (1 << m);i++) f[i] = 1e18;
f[0] = 0;
for(int S = 0;S < (1 << m);++S)
for(int i = 0;i < m;i++)
if((~S >> i) & 1)
ckmin(f[S ^ (1 << i)],f[S] + Calc(S,i));
printf("%.3lf\n",f[(1 << m) - 1]);
return 0;
}