树上逆序对
题意
对于一棵有根树,定义树上的逆序对为满足 a a ai< a a afai 的二元对 ( i , (i, (i,f a i ) ~ai~) ai ) , 其中 f f fai 表示结点 i i i 的父亲结点
对于一棵 k k k 叉树, 结点 i i i 的子节点的编号集合为 [ 1 , n ] ∩ [ k ( i − 1 ) + 2 , k i + 1 ] [1,n]∩[k(i−1)+2,ki+1] [1,n]∩[k(i−1)+2,ki+1] 中的所有整数
给定 n n n 个结点的权值 a a a1, a a a2,…, a a an , 对于 k = 1 , 2 , … , n − 1 k=1,2,…,n−1 k=1,2,…,n−1 求出当这 n n n 个结点构成一棵 k k k 叉树时树上逆序对数
1 ≤ \leq ≤ n n n ≤ \leq ≤ 2 e 5 2e^5 2e5 , 1 ≤ \leq ≤ a a ai ≤ \leq ≤ 1 0 9 10^9 109
思路
这里我们先考虑枚举 k k k ,再枚举每个点的所有孩子节点,发现这样的复杂度是 n 2 n^2 n2 , 而且这样是很难优化进去的
然后我们换个思考方向,我们这里先枚举每个点 ,再枚举 k k k , 但这里虽然听起来还是 n 2 n^2 n2 的,但里面复杂度是 O ( a c ) O(ac) O(ac) 的,为啥勒
我们将每个点先按权重从小到大,再按位置从小到大排序,把这些点一个一个插进树状数组里面,计算每个点的对答案的贡献,同时不重不漏
代码
#include <stdio.h>
#include <cstdio>
#include <queue>
#include <string>
#include <cstring>
#include <map>
#include <vector>
#include <cmath>
#include <vector>
#include <algorithm>
#include <set>
#include <iostream>
using namespace std;
const int maxn=2e5+100;
int n;
int tree[maxn << 2];
int read() {
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*f;
}
void write(int x) {
if(x<0) putchar('-'),x=-x;
if(x>9) write(x/10);
putchar(x%10+'0');
}
struct node{
int id,val;
bool operator<(const node& rhs)const {
if(val!=rhs.val) return val<rhs.val;
return id<rhs.id;
}
}a[maxn];
int lowbit(int k) {return k&(-k);}
void add(int x,int k) {
while (x <= n) {
tree[x] += k;
x += lowbit(x);
}
}
int sum(int x) {
int ans = 0;
while (x != 0) {
ans += tree[x];
x -= lowbit(x);
}
return ans;
}
int ans[maxn];
void solve(){
n=read();
for(int i=1;i<=n;++i) a[i].val=read(),a[i].id=i;
sort(a+1,a+1+n);
for(int i=1;i<=n;i++){
for(int j=1;j<n;j++){
int x=(j)*(a[i].id-1)+2;
int y=min(j*a[i].id+1,n);
if(x>n) break;
ans[j]+=sum(y)-sum(x-1);
}
add(a[i].id,1);
}
for(int j=1;j<n;++j){
write(ans[j]);
printf(" ");
}
}
int main() {
solve();
return 0;
}
/*
*/