51nod 1533 一堆的堆 & Codeforces538F A Heap of Heaps(树状数组||主席树)

xiaoxiao2021-02-28  26

题意:现在有一个长度为n的数组  a1, a2, ..., an  。然后对于k从1到 n-1分别对该数组建k叉堆。现在要统计对于每一个k叉堆,里面有多少结点是不满足最小堆的性质的。即值比父亲的要小的结点有多少个。

k叉堆的定义是这样的:数组的下标从1到n编号,对于某一个编号为v的结点,他的k个儿子编号是  k(v  1) + 2, ..., kv + 1  (如果其中某些编号超出n,那些编号就不要)。在k叉堆中,除了根以外,每一个结点都有一个父亲。p(v)表示结点v的父亲。那么一个非根结点不满足最小堆性质当且仅当  av < ap(v)

思路:(点击打开链接这篇写的非常好了已经) 完全k叉树当k很大的时候,其实层数是特别小的,所以感觉暴力是可以的。注意到一个完全k叉树下v节点的儿子的公式是:k*(v-1)+2...kv+1,相应的父节点的公式是 (v+k-2)/k。儿子的编号是连续的,如果我们可以对每个节点快速的求出连续编号的节点有多少个数比它小我们就可以快速的更新答案了,但是如果对每个节点都这样做的话就至少是一个O(n^2)级别的做法。注意到对于一棵完全k叉树来说,只有内节点才需要统计,叶节点并不需要。而对于一个大小为n的完全k叉树来说,内节点的个数是O(n/k)的,因此总的内节点个数就是n/1+n/2+n/3+...n/n-1,即O(nlogn)。

然后就是单次询问一段连续的区间里有多少个数比v小。这里我没有想到什么好的简便的方法,不过函数式线段树是一个解决方法。root[i]表示的是用a[i]~a[n]的值建立的线段树,当我需要询问某个区间[l,r]的小于等于v的数有多少个数时,只需要query(root[l],1,v)-query(root[r],1,v)即可。空间复杂度是O(nlogn),时间复杂度是单次询问O(logn),最后总的复杂度就是O(nlog^2 n)

(建树和查询时都要注意一下。。是倒着建的树)

还有个树状数组的做法,可以讲a离散化后排序,然后按顺序插入,这样每次询问的儿子区间内的数都是比它小的。

51nod的时限缩短了,评测机也没cf那么快,所以主席树的做法只能在cf上AC

树状数组代码:

#include<bits/stdc++.h> using namespace std; const int maxn = 2e5+5; int n, tree[maxn], ans[maxn]; struct node { int id, val; bool operator < (const node &a)const { return val < a.val; } }a[maxn]; int lowbit(int x) { return x&(-x); } void update(int pos, int val) { while(pos < maxn) { tree[pos] += val; pos += lowbit(pos); } } int query(int pos) { int res = 0; while(pos) { res += tree[pos]; pos -= lowbit(pos); } return res; } void solve() { memset(tree, 0, sizeof(tree)); memset(ans, 0, sizeof(ans)); int p; for(int i = 1; i <= n; i = p) { p = i; while(p <= n && a[p].val == a[i].val) p++; for(int j = i; j < p; j++) { int id = a[j].id; for(int k = 1; k <= n-1 && k*(id-1)+2 <= n; k++) ans[k] += query(min(n, k*id+1))-query(k*(id-1)+1); } for(int j = i; j < p; j++) update(a[j].id, 1); } for(int i = 1; i <= n-1; i++) printf("%d%c", ans[i], i==n-1 ? '\n' : ' '); } int main(void) { while(cin >> n) { for(int i = 1; i <= n; i++) scanf("%d", &a[i].val), a[i].id = i; sort(a+1, a+1+n); solve(); } return 0; }

主席树代码:

#include<bits/stdc++.h> using namespace std; const int maxn = 2e5+5; int a[maxn], Hash[maxn]; int lson[maxn<<5], rson[maxn<<5], sum[maxn<<5]; int T[maxn]; int n, tot, ans[maxn]; int build(int l, int r) { int rt = ++tot; sum[rt] = 0; if(l < r) { int mid = (l+r)/2; lson[rt] = build(l, mid); rson[rt] = build(mid+1, r); } return rt; } int update(int pre, int l, int r, int x) { int rt = ++tot; lson[rt] = lson[pre], rson[rt] = rson[pre], sum[rt] = sum[pre]+1; if(l < r) { int mid = (l+r)/2; if(x <= mid) lson[rt] = update(lson[pre], l, mid, x); else rson[rt] = update(rson[pre], mid+1, r, x); } return rt; } int query(int u, int v, int l, int r, int i, int j) { if(i > j) return 0; if(i <= l && j >= r) return sum[v]-sum[u]; int mid = (l+r)/2; int ans = 0; if(i <= mid) ans += query(lson[u], lson[v], l, mid, i, j); if(j > mid) ans += query(rson[u], rson[v], mid+1, r, i, j); return ans; } int main(void) { while(cin >> n) { for(int i = 1; i <= n; i++) scanf("%d", &a[i]), Hash[i] = a[i]; sort(Hash+1, Hash+1+n); int d = unique(Hash+1, Hash+1+n)-Hash-1; for(int i = 1; i <= n; i++) a[i] = lower_bound(Hash+1, Hash+1+d, a[i])-Hash; tot = 0; T[n+1] = 0; memset(sum, 0, sizeof(sum)); for(int i = n; i >= 1; i--) T[i] = update(T[i+1], 1, d, a[i]); memset(ans, 0, sizeof(ans)); for(int k = 1; k <= n-1; k++) { int maxId = (n+k-2)/k; for(int id = 1; id <= maxId; id++) { int L = k*(id-1)+2; int R = min(n, k*id+1); ans[k] += query(T[R+1], T[L], 1, d, 1, a[id]-1); //注意建树是反的 } } for(int i = 1; i <= n-1; i++) printf("%d%c", ans[i], i==n-1 ? '\n' : ' '); } return 0; }

转载请注明原文地址: https://www.6miu.com/read-1000103.html

最新回复(0)