CodeForces 665E. Beautiful Subarrays(字典樹)(貪心)(異或字首和)
阿新 • • 發佈:2020-07-11
題意:給定一個長度為n(1 <= n <= 1e6)的陣列a[i](0 <= a[i] <= 1e9)和k(1 <= k <= 1e9)。求有多少個區間[l, r]是合法的。我們認為一個區間是合法的,當且僅當\(a[l]\oplus a[l + 1]\oplus a[l + 2] \oplus ... a[r] >= k\)。
分析:對於一個區間是否合法,我們可以先求出異或的字首和,對於一個區間[l, r]合法,代表者\(sum[l - 1] \oplus sum[r] >= k\),我們列舉每個當前的字首和,然後用一個數據結構\(trie樹\)維護,查詢之前多有少個字首和和當前的字首和異或起來>=k
字典樹的結點個數要開多少呢,可以看出\(a[i]\in{[0, 1e9]}\),\(log(1e9) == 30\),那麼我們可以開\(1e6 * 30 = 3e7\)個結點。
字典樹不僅能在葉子結點維護插入的數的數量,而且能在每個結點上累加,代表這裡曾今被插過的數的數量。
#include <iostream> #include <cstdio> #include <cstring> #include <vector> #include <algorithm> using namespace std; using LL = long long; const int N = 30 * 1000005; const int M = 1000005; int tr[N][2], idx; int cnt[N]; int a[M]; int sum[M]; int n, k; void insert(int val) { int p = 0; for (int i = 30; i >= 0; --i) { int u = (val >> i) & 1; if (!tr[p][u]) tr[p][u] = ++idx; p = tr[p][u]; ++cnt[p]; } } int query(int val) { int p = 0, sum = 0; for (int i = 30; i >= 0; --i) { int u = (val >> i) & 1; int c = (k >> i) & 1; if (c == 0) { int k = tr[p][!u]; sum += cnt[k]; if (tr[p][u] == 0) return sum; p = tr[p][u]; } else { p = tr[p][!u]; if (p == 0) return sum; } } return sum + cnt[p]; } int main() { scanf("%d%d", &n, &k); for (int i = 1; i <= n; ++i) { scanf("%d", &a[i]); sum[i] = sum[i - 1] ^ a[i]; } LL res = 0; for (int i = 1; i <= n; ++i) { res += query(sum[i]); insert(sum[i]); } for (int i = 1; i <= n; ++i) { if (sum[i] >= k) ++res; } printf("%lld\n", res); return 0; }