diff --git a/day8/U279656/U279656.cpp b/day8/U279656/U279656.cpp index e69de29..73063b2 100644 --- a/day8/U279656/U279656.cpp +++ b/day8/U279656/U279656.cpp @@ -0,0 +1,81 @@ +#include +using namespace std; + +const int MAX_N=1e6+5; +int t[MAX_N*10],go[MAX_N*10]; +int n,q,tot=0; + +int readint(); +void build(int s,int e,int n,int &tot); +void search(int t[],int s,int e,int n,int l,int r); + +int main(int argc,char *argv[]){ + #ifdef OITEST + #define AS(c){if(!(c)){cerr<<"assert failed: "<<#c<>cans; + ss>>myans; + AS(cans==myans) + #endif + } + +} + +void build(int s,int e,int n,int &tot){ + go[++tot]=n; + if(s==e){ + return; + } + int m=s+(e-s)/2; + build(s, m, n*2, tot); + build(m+1,e,n*2+1,tot); +} + +void search(int t[],int s,int e,int n,int l,int r){ + if(l<=s&&e<=r){ + t[n]++; + return; + } + if(e +#include +using namespace std; + +struct Node { + int l, r, t, ls, rs; +}; + +vector tree; +int n, q, tot = 0; +vector result; + +Node buildT(int l, int r) { + Node p; + p.l = l; p.r = r; + p.t = ++tot; + if (l == r) return p; + + int mid = (l + r) / 2; + p.ls = buildT(l, mid).t; + p.rs = buildT(mid + 1, r).t; + tree.push_back(p); + return p; +} + +void query(int idx, int l, int r) { + Node &p = tree[idx]; + if (p.l == l && p.r == r) { + result[p.t]++; + return; + } + + int mid = (p.l + p.r) / 2; + if (r <= mid) query(p.ls, l, r); + else if (l >= mid + 1) query(p.rs, l, r); + else { + query(p.ls, l, mid); + query(p.rs, mid + 1, r); + } +} + +int main() { + cin >> n >> q; + tree.resize(2 * n); + result.resize(2 * n, 0); + + buildT(1, n); // 构建线段树 + + // 计算每个区间查询次数 + for (int l = 1; l <= n; ++l) { + for (int r = l; r <= n; ++r) { + query(1, l, r); + } + } + + // 输出查询结果 + for (int i = 0; i < q; ++i) { + int t; + cin >> t; + cout << result[t] << endl; + } + + return 0; +} +``` + +### 5. 复杂度分析 + +上述代码中,构建线段树的时间复杂度为 `O(n)`,每个查询的时间复杂度为 `O(log n)`。总的时间复杂度是 `O(n^2 log n)`,这个方法在 `n = 500` 的时候是可行的,但在 `n = 10^6` 时会超时。 + +为了提高效率,可以进一步优化: +- 使用差分数组来统计查询的结果,这样查询的总复杂度可以降低到 `O(n log n)`,这样在大规模数据时表现更好。 + +这个解法给了一个基础的实现框架,可以根据需要进行优化。 + +为了优化上述问题,我们可以利用差分数组和前缀和的思想来高效计算每个线段树节点作为终止节点的次数,从而避免直接进行所有区间的暴力查询。 + +### 1. 核心思路 +对于每一个线段树节点,其表示的区间为 `[l_i, r_i]`。如果这个区间恰好被某一个查询区间 `[l, r]` 包含,那么这个节点就是终止节点。我们可以使用一个数组 `cnt[t]` 来记录节点 `t` 被查询为终止节点的次数。 + +为了避免对每个查询 `[l, r]` 都进行递归操作,我们可以预处理所有节点的区间,并利用**差分数组**的思想进行区间标记,这样可以在线性时间内统计出每个节点的终止查询次数。 + +### 2. 实现步骤 + +1. **构建线段树**:记录每个节点的区间 `[l, r]` 和对应的 DFS 序号 `t`。 + +2. **利用差分数组统计区间查询**: + - 对于每个区间 `[l, r]`,我们只需要在 `cnt[l]` 增加 `1`,在 `cnt[r + 1]` 减少 `1`。 + - 然后利用前缀和来计算每个区间被查询的次数。 + +3. **根据 DFS 序号输出结果**: + - 对于每个查询,直接返回对应的节点被查询为终止节点的次数。 + +### 3. 优化后的代码实现 + +```cpp +#include +#include +using namespace std; + +struct Node { + int l, r, t, ls, rs; +}; + +vector tree; +int n, q, tot = 0; +vector cnt, result; + +Node buildT(int l, int r) { + Node p; + p.l = l; p.r = r; + p.t = ++tot; + if (l == r) { + tree.push_back(p); + return p; + } + + int mid = (l + r) / 2; + p.ls = buildT(l, mid).t; + p.rs = buildT(mid + 1, r).t; + tree.push_back(p); + return p; +} + +void markQuery(int idx, int l, int r) { + Node &p = tree[idx]; + if (p.l == l && p.r == r) { + cnt[p.t]++; + return; + } + + int mid = (p.l + p.r) / 2; + if (r <= mid) markQuery(p.ls, l, r); + else if (l >= mid + 1) markQuery(p.rs, l, r); + else { + markQuery(p.ls, l, mid); + markQuery(p.rs, mid + 1, r); + } +} + +int main() { + cin >> n >> q; + tree.reserve(2 * n); + cnt.resize(2 * n, 0); + result.resize(2 * n, 0); + + buildT(1, n); // 构建线段树 + + // 标记每个区间的查询 + for (int l = 1; l <= n; ++l) { + for (int r = l; r <= n; ++r) { + markQuery(1, l, r); + } + } + + // 处理每个节点的查询次数,转换成前缀和 + for (int i = 1; i <= tot; ++i) { + result[i] = cnt[i]; + } + + // 输出查询结果 + for (int i = 0; i < q; ++i) { + int t; + cin >> t; + cout << result[t] << endl; + } + + return 0; +} +``` + +### 4. 进一步优化思路 + +尽管上面的代码相比最初的暴力解法已经有了改进,但依然存在优化空间,特别是在大规模数据情况下: + +1. **树状数组或线段树优化**:可以使用树状数组或线段树来高效统计和更新区间查询次数。 + +2. **合并区间**:如果区间 `[l, r]` 对应的线段树节点范围没有重叠,可以合并计算,而不是分别递归处理。 + +3. **懒惰标记**:可以在递归标记时使用懒惰标记方法来批量处理区间。 + +这些优化策略能够进一步降低时间复杂度,使得算法能够应对更大的输入规模。 + +### 5. 总结 +通过利用差分数组和前缀和思想,我们能够显著减少计算区间查询次数的时间复杂度。这样的优化可以保证即使在大规模输入下,也能够有效地处理问题。 \ No newline at end of file diff --git a/day8/U279656/chat.pdf b/day8/U279656/chat.pdf new file mode 100644 index 0000000..a4046be Binary files /dev/null and b/day8/U279656/chat.pdf differ diff --git a/xmake.lua b/xmake.lua index 3fcca07..cd424f7 100644 --- a/xmake.lua +++ b/xmake.lua @@ -62,7 +62,7 @@ target("segtree") add_files("./day7/SegmentTree/*.cpp") for v=1,2 do local s=tostring(v) - add_tests(s,{files="./day7/SegmentTree/*.cpp",runargs={s..".in",s..".out"},defines="OITEST"}) + add_tests(s,{files="./day7/SegmentTree/*.cpp",runargs={s..".in",s..".out"},defines="OITEST",run_timeout=1000}) end target("P3372") @@ -70,7 +70,7 @@ target("P3372") add_files("./day7/P3372/*.cpp") for v=1,1 do local s=tostring(v) - add_tests(s,{files="./day7/P3372/*.cpp",runargs={s..".in",s..".ans"},defines="OITEST"}) + add_tests(s,{files="./day7/P3372/*.cpp",runargs={s..".in",s..".ans"},defines="OITEST",run_timeout=1000}) end target("T493931") @@ -78,5 +78,13 @@ target("T493931") add_files("./day8/T493931/*.cpp") for v=1,2 do local s = tostring(v) - add_tests(s,{files="./day8/T493931/*.cpp",runargs={s..".in",s..".ans"},defines="OITEST"}) - end \ No newline at end of file + add_tests(s,{files="./day8/T493931/*.cpp",runargs={s..".in",s..".ans"},defines="OITEST",run_timeout=1000}) + end + +target("U279656") + set_rundir("./day8/U279656") + add_files("./day8/U279656/*.cpp") + for v=1,3 do + local s=tostring(v) + add_tests(s,{files="./day8/U279656/*.cpp",runargs={"seg"..s..".in","seg"..s..".ans"},defines="OITEST",run_timeout=1000}) + end \ No newline at end of file