想用博客记录一下自己做到的好题,希望对以后的自己和大家有帮助
Codeforces Round 1016 Div.3 G
题目大意
在长度为 \(n\) 的数组, 价值为 \(f = max(b_{l} \oplus b_{r})\) , 如果数组价值大于 \(k\), 那么我们称其为美丽的
现在要在原数组中寻找最短的连续子数组, 且满足\(f \ge k\)
input
组数 \(t\)
接下来 $ 2 * t$ 行:
\(~~~\)数组长度 \(n\)
\(~~~\)\(n\) 个整数 \(a_{1...n}\)
output
最短长度 \(l\) , 若找不到则输出 \(-1\)
题解
0-1 trie树
求异或和最大, 可以用0-1trie树来维护, 除此之外我们还要额外记录一下某点满足 \(f \ge k\) 的最小长度
我们从左往右依次向trie树中插入 \(a_i\) , 同时查询满足 $a_j \oplus a_i \ge k $ 的最大下标 \(j\), 同时维护最小值 \(minn\)
有点像数位dp的思路, 我们把 $a_j \oplus a_i \ge k $ 拆成两部分, $a_j \oplus a_i > k $ 与 $a_j \oplus a_i = k $, 于是有接下来的分类讨论
从高到底枚举k的位数
- 如果二级制的 \(k\) 中第 \(t\) 位为 1 , 我们必须沿着 1 走下去
- 反之为 0 , 如果存在 1 的道路, 我们查询最大的能到达 1 的下标, 此时为 $a_j \oplus a_i > k $; 但我们仍要按照 0 继续向下走, 去判断 $a_j \oplus a_i = k $ 的情况
- 如果成功走到底, 特判一下 $a_j \oplus a_i = k $
code
用vector维护trie树
struct Node
{
int chl[2] = {0, 0};
int last;//存最后一次到达该点的下标
};
void solve()
{
idx = 0; vector<Node> tr(1);
int minn = INF;
scanf("%d%d", &n, &k);
for(int i = 1; i <= n; i++)
{
int a; scanf("%d", &a);
if(k == 0) minn = 0;
int u = insert(tr, a, i);
if(u != -1)
minn = min(i - u, minn);
}
if(minn != INF)
printf("%d\n", minn + 1);
else puts("-1");
}
因为有 \(t\) 组输入, 如果用数组模拟的话需要每次清零, 耗费大量时间和空间
int insert(vector<Node> &tr, int s, int t)
{
int p = 0, maxx = -1, q = 0;
tr[p].last = max(tr[p].last, t);
for(int i = 29; i >= 0; i--)
{
int u = s >> i & 1;
int v = k >> i & 1;
if(q != -1 && v == 0 && tr[q].chl[u ^ 1]) maxx = max(maxx, tr[tr[q].chl[u ^ 1]].last);// ">" 的情况
if(q != -1 && tr[q].chl[u ^ v]) q = tr[q].chl[u ^ v];
else q = -1;
tr[p].last = max(tr[p].last, t);
if(!tr[p].chl[u])
{
tr[p].chl[u] = tr.size();
tr.push_back(Node());
}
p = tr[p].chl[u];
}
if(q != -1) maxx = max(maxx, tr[q].last); // "=" 的情况
tr[p].last = max(tr[p].last, t);
return maxx;
}