正睿 25 年联赛联合训练 Day 15
正睿 25 年联赛联合训练 Day 15
得分
| T1 | T2 | T3 | 总分 | 排名 |
|---|---|---|---|---|
| \(100\) | \(20\) | \(80\) | \(200\) | 无 |
- T2 没有特判 \(p=0/1\) 的情况,\(100\to 20\)。
- T3 被卡常,\(100\to 80\)。
题解
T1 红黑树
简单题,设 \(dp(i,j,0/1)\) 表示当前遍历 \(i\) 子树,子树内所有空节点到 \(i\) 路径上都经过 \(j\) 个黑点,当前点放白点 / 黑点是否可行。转移直接暴力枚举 \(j\) 然后转移即可,最后根据状态输出方案即可。复杂度 \(O(n^2)\)。
#include <bits/stdc++.h>
using namespace std;
const int Maxn = 5e3 + 5;
const int Inf = 2e9;
int n, fa[Maxn], rt, deg[Maxn];
int head[Maxn], edgenum;
struct node {
int nxt, to;
}edge[Maxn];
void add(int u, int v) {
deg[u]++;
edge[++edgenum] = {head[u], v}; head[u] = edgenum;
}
bool dp[Maxn][Maxn][2];
void dfs(int x) {
if(!deg[x]) {
dp[x][0][0] = dp[x][1][1] = 1; return;
}
else if(deg[x] == 1) {
for(int i = head[x]; i; i = edge[i].nxt) {
int to = edge[i].to;
dfs(to);
dp[x][1][1] = dp[to][0][0];
}
return ;
}
for(int i = 0; i <= n; i++) dp[x][i][0] = 1;
for(int i = 1; i <= n; i++) dp[x][i][1] = 1;
for(int i = head[x]; i; i = edge[i].nxt) {
int to = edge[i].to;
dfs(to);
for(int j = 0; j <= n; j++) {
dp[x][j][0] &= dp[to][j][1];
if(j) dp[x][j][1] &= (dp[to][j - 1][0] | dp[to][j - 1][1]);
}
}
}
int col[Maxn];
void dfs2(int x, int num, int c) {
col[x] = c;
for(int i = head[x]; i; i = edge[i].nxt) {
int to = edge[i].to;
if(!c) dfs2(to, num, 1);
else {
if(dp[to][num - 1][0]) dfs2(to, num - 1, 0);
else dfs2(to, num - 1, 1);
}
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> fa[i];
if(fa[i]) add(fa[i], i);
else rt = i;
}
dfs(rt);
int r = -1, c = -1;
for(int i = 0; i <= n; i++) {
if(dp[rt][i][0] == 1) {
r = i, c = 0; break;
}
if(dp[rt][i][1] == 1) {
r = i, c = 1; break;
}
}
if(r == -1) cout << "Impossible\n";
else {
dfs2(rt, r, c);
for(int i = 1; i <= n; i++) {
if(col[i] == 0) cout << "R";
else cout << "B";
}
}
return 0;
}
T2 旅行
首先先考虑对于单个航班怎么查询最优,显然如果要退出我们要让它尽可能早退出,所以应该将停运概率从大到小排序。然后考虑 \(n\) 条航线怎么做,首先显然的一点是我们不会查询一条航线到一半然后退出,因为这样一定不优。所以我们一定是按照一个固定的排列 \(c_1,c_2,\cdots c_n\) 去查询的。
那么现在我们要找到这个排列,让期望最优。我们先考虑最后的 dp 是怎样求的,显然由于求的是期望,所以倒着 dp 比较好写。令 \(E_i\) 表示查询 \(c_i,c_{i+1},\cdots,c_n\) 的期望,那么我们有转移方程:
那么观察后发现这个式子实际上可以写成 \(E_i=aE_{i+1}+b\) 的形式,也就是说对于任意一条航线 \(i\),我们都有一个二元组 \((a_i,b_i)\),然后现在需要通过排列这些二元组使得最后的 \(E\) 最小。考虑利用邻项交换法简单贪心,就可以求出合法的 \(c\),然后求出答案即可。复杂度是 \(O(\sum k+n\log n)\) 的。
最后这道题有一个 corner case,当某个数列中有 \(p=1\) 时是不需要查询的,而当某个数列检查到只剩 \(p=0\) 时也不用检查了。司马出题人捆测 + 依赖 + 超水大样例直接给正解干成 \(20\) 分了。
代码实现略有不同。
#include <bits/stdc++.h>
#define double long double
using namespace std;
const int Maxn = 500 + 5;
const int Inf = 2e9;
const double eps = 1e-9;
int n, k[Maxn], id[Maxn], m;
double p[Maxn][Maxn];
double calc(int x, int y) {
double ret = 0, res = 0;
for(int i = k[y]; i >= 1; i--) ret = (ret + 1) * (1 - p[y][i]);
for(int i = k[x]; i >= 1; i--) res = (res + 1) * (1 - p[x][i]) + (ret + 1) * p[x][i];
return res;
}
bool cmp(int x, int y) {
double r1 = calc(x, y), r2 = calc(y, x);
return r2 > r1;
}
double dp[Maxn][Maxn];
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> k[i]; id[i] = i;
for(int j = 1; j <= k[i]; j++) cin >> p[i][j];
sort(p[i] + 1, p[i] + k[i] + 1, [](double x, double y){return x > y;});
while(k[i] > 0 && p[i][k[i]] == 0) k[i]--;
if(p[i][1] != 1 && k[i]) id[++m] = i;
}
sort(id + 1, id + m + 1, cmp);
for(int i = m; i >= 1; i--) {
for(int j = k[id[i]]; j >= 1; j--) {
dp[i][j] = (dp[i][j + 1] + 1) * (1 - p[id[i]][j]) + (dp[i + 1][1] + 1) * p[id[i]][j];
}
}
cout << fixed << setprecision(10) << dp[1][1] << '\n';
return 0;
}
T3 数字
题目中小 Y 的操作实际上是对当前 \(x\) 循环左移一位。这个直接做比较困难,我们希望把循环左移这个操作放到最开头进行,然后寻找等效的异或数字,这样的话可以简化题意。
那么我们先对 \(x\) 循环左移,此时假如我们在 \(i\) 操作结束后进行这个操作,那么实际上相当于我们在异或 \(a_1\sim a_i\) 的时候对所有 \(a\) 都进行了循环左移再异或。那么我们就容易处理出在第 \(i\) 次操作后进行循环左移操作的等效异或数字 \(b_i\)。
此时我们的所有数字就是先循环左移再异或 \(b_i\),实际上此时第一步操作都不是很重要了。那么小 Y 的操作就是对于一个数 \(x\) 异或上 \(b_i\) 使得它最小,而我们要找出这个最小值的最大值。考虑枚举 \(x\) 不可行,所以把问题反过来,我们枚举 \(b_i\),看哪些 \(x\) 异或它最小,求出这些最小值的最大值即可。
既然要求异或值最小,我们就考虑建出 01-Trie,然后从上往下遍历,遍历到叶子节点的时候我们会得到一条路径,此时有一些初始的 \(x\) 在求最小值时走的一定是这条路径,而这些最小值中的最大值是不难求出的:当路径上的节点只有一个儿子的时候,答案的这一位可以取到 \(1\),不然的话就只能取 \(0\)。那么我们要的是所有 \(x\) 算出来的最小值的最大值,自然只需要把每个在叶子节点处算出的答案取最大即可。复杂度是 \(O(nm)\) 的,不过需要搜索的时候剪一下枝才能过。
#include <bits/stdc++.h>
using namespace std;
const int Maxn = 1e6 + 2;
const int Inf = 2e9;
bool Beg;
int n, m, T, a[Maxn];
int pre[Maxn], suf[Maxn], sta[Maxn];
struct node {
int son[2];
}t[Maxn * 30];
int tot = 1;
void insert(int x) {
int u = 1;
for(int i = n; i >= 1; i--) {
int c = (x >> i - 1) & 1;
if(!t[u].son[c]) t[u].son[c] = ++tot;
u = t[u].son[c];
}
}
int ans = 0, ans2 = 0;
void dfs(int x, int dep, int ret) {
if((ret >> dep) < (ans >> dep)) return ;
if(!dep) {
if(ret > ans) ans = ret, ans2 = 1;
else if(ret == ans) ans2++;
return ;
}
if(!t[x].son[0]) dfs(t[x].son[1], dep - 1, ret | (1 << dep - 1));
else if(!t[x].son[1]) dfs(t[x].son[0], dep - 1, ret | (1 << dep - 1));
else {
dfs(t[x].son[0], dep - 1, ret);
dfs(t[x].son[1], dep - 1, ret);
}
}
bool End;
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m >> T;
for(int i = 1; i <= m; i++) cin >> a[i];
for(int i = m; i >= 1; i--) suf[i] = suf[i + 1] ^ a[i];
for(int i = 1; i <= m + 1; i++) {
int p = ((a[i] >> n - 1) | (a[i] << 1)) & ((1 << n) - 1);
pre[i] = pre[i - 1] ^ p;
sta[i - 1] = pre[i - 1] ^ suf[i];
}
for(int i = 0; i <= m; i++) insert(sta[i]);
dfs(1, n, 0);
cout << ans << '\n';
if(T == 1) cout << ans2 << '\n';
// cerr << (&Beg - &End) / 1024.0 / 1024.0 << '\n';
return 0;
}

浙公网安备 33010602011771号