CCPC河南省赛B-树上逆序对| 离线处理|树状数组 + 线段树维护逆序对 + dfs序 + 离散化

B题地址:树上逆序对

有两个思路
方法一:线段树离线 + 树状数组或者线段树维护区间和
0:离散化,离线存储输入的operation操作序列。
①:先线段树在dfs序上离线处理好整一棵树:在dfs序上先查询"加入当前结点的逆序对权值和"并记录,再加入当前这个节点;dfs完毕后,就已经记录好每个结点的dfs序出入时间戳(转化成区间问题了)和每个
②:使用树状数组或者新的线段树在dfs序上插入逆序对权值

为什么能这样呢?因为dfs序维护了每个结点遍历的顺序,每个结点的dfs序时间戳肯定比它的子孙结点们小,离线后就可以重新再更新新的树状数组或线段树,加点权(下标是dfs序、权值是每个点加入前,其它所有点它的逆序对个数)。

方法二:主席树,利用主席树的历史版本在线建树,不受影响,待补

写了方法一的,已AC~

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5+10;
const int inf = 0x3f3f3f3f;
vector<int> vec;
vector<int> g[maxn];
int n,m;
int a[maxn],in[maxn],out[maxn],cnt = 0,num = 0,unum = 0;
int tree[maxn*4];
ll res[maxn],c[maxn*2];

struct node{
    int type;
    int u;
    int x;
}op[maxn];

//线段树 单点更新 区间查询
void pushup(int o){
    tree[o] = tree[o<<1] + tree[o<<1|1];
}

void build(int o,int l,int r){
    if(l == r){
        tree[o] = 0;
        return;
    }
    int mid = (l + r) >> 1;
    build(o<<1,l,mid);
    build(o<<1|1,mid+1,r);
    pushup(o);
}

void update(int o,int l,int r,int pos,ll v){
    if(l == r){
        tree[o] += v;
        return;
    }
    int mid = (l + r) >> 1;
    if(pos <= mid) update(o<<1,l,mid,pos,v);
    else update(o<<1|1,mid+1,r,pos,v);
    pushup(o);
}

ll query(int o,int l,int r,int ql,int qr){
    if(l > r) return 0;
    if(ql <= l && r <= qr) return tree[o];
    int mid = (l + r) >> 1;
    ll result = 0;
    if(ql <= mid) result += query(o<<1,l,mid,ql,qr);
    if(qr > mid) result += query(o<<1|1,mid+1,r,ql,qr);
    return result;
}

//树状数组 维护区间和
int lowbit(int x){
    return x & -x;
}

void add(int x,int v){
    while(x < maxn){
        c[x] += v;
        x += lowbit(x);
    }
}

ll ask(int x){
    if(x >= maxn) return 0;
    ll res = 0;
    while(x){
        res += c[x];
        x -= lowbit(x);
    }
    return res;
}

ll rangeAsk(int l,int r){
    return ask(r) - ask(l - 1);
}

void dfs(int x,int father){
    in[x] = ++cnt;
    int pos = lower_bound(vec.begin(),vec.end(),a[x]) - vec.begin();
    res[x] = query(1,1,unum,pos+1,unum); //当前(除了以x为根的子树外)  全局的pos+1~最大值的逆序对
    update(1,1,unum,pos,1);
    for(int i=0;i<g[x].size();i++){
        int v = g[x][i];
        if(v != father){
            dfs(v,x);
        }
    }
    update(1,1,unum,pos,-1);
    out[x] = cnt;
}

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) {
        scanf("%d",&a[i]);
        vec.push_back(a[i]);
    }
    for(int i=1;i<=n-1;i++){
        int fa;
        scanf("%d",&fa);
        g[fa].push_back(i+1);
    }
    num = n;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&op[i].type,&op[i].u);
        if(op[i].type == 1){
            scanf("%d",&op[i].x);
            a[++num] = op[i].x;
            vec.push_back(a[num]);
            g[op[i].u].push_back(num);
        }
    }
    //离散化
    vec.push_back(-inf);
    sort(vec.begin(),vec.end());
    vec.erase(unique(vec.begin(),vec.end()),vec.end());
    unum = vec.size() - 1;
    //建立线段树
    build(1,1,unum);
    dfs(1,0); //dfs上插入结点
    //建立树状数组 维护区间和
    for(int i=1;i<=n;i++){
        add(in[i],res[i]); //先把原始树上的点插入好
    }
    int id = n;
    //m个操作 离线加点
    for(int i=1;i<=m;i++){
        if(op[i].type == 1){
            id++;
            add(in[id],res[id]);
        }else{
            //子树区间对应时间戳: in[op[i].u] ~ out[op[i].u]
            printf("%lld\n",ask(cnt) - rangeAsk(in[op[i].u],out[op[i].u]));
        }
    }
    return 0;
} 

原文地址:https://www.cnblogs.com/fisherss/p/12233139.html

时间: 2024-12-21 07:56:35

CCPC河南省赛B-树上逆序对| 离线处理|树状数组 + 线段树维护逆序对 + dfs序 + 离散化的相关文章

XJTUOJ wmq的队伍(树状数组求 K 元逆序对)

题目链接:http://oj.xjtuacm.com/problem/14/[分析]二元的逆序对应该都会求,可以用树状数组.这个题要求K元,我们可以看成二元的.我们先从后往前求二元逆序对数, 然后对于每一个数就可以求出在这个数后面的比他小的数的数量.然后我们再加一元时,当前扫到a[i],那么在树状数组中,对于那些比他大的数的 逆序对数+=上一元a[i]的逆序对数. #include <bits/stdc++.h> #define met(a,b) memset(a,b,sizeof a) #d

hdu 4417 Super Mario(离线树状数组|划分树)

Super Mario Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 2584    Accepted Submission(s): 1252 Problem Description Mario is world-famous plumber. His "burly" figure and amazing jumping a

Permutation UVA - 11525(值域树状数组,树状数组区间第k大(离线),log方,log)

Permutation UVA - 11525 看康托展开 题目给出的式子(n=s[1]*(k-1)!+s[2]*(k-2)!+...+s[k]*0!)非常像逆康托展开(将n个数的所有排列按字典序排序,并将所有排列编号(从0开始),给出排列的编号得到对应排列)用到的式子.可以想到用逆康托展开的方法.但是需要一些变化: for(i=n;i>=1;i--) { s[i-1]+=s[i]/(n-i+1); s[i]%=(n-i+1); } 例如:n=3时,3=0*2!+0*1!+3*0!应该变为3=1

HDU 6318 Swaps and Inversions 思路很巧妙!!!(转换为树状数组或者归并求解逆序数)

Swaps and Inversions Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 2315    Accepted Submission(s): 882 Problem Description Long long ago, there was an integer sequence a.Tonyfang think this se

POJ 2299 Ultra-QuickSort (求逆序数:离散化+树状数组或者归并排序求逆序数)

Ultra-QuickSort Time Limit: 7000MS   Memory Limit: 65536K Total Submissions: 55048   Accepted: 20256 Description In this problem, you have to analyze a particular sorting algorithm. The algorithm processes a sequence of n distinct integers by swappin

[树状数组]求排列的逆序数

求排列的逆序数 题目描述 在Internet上的搜索引擎经常需要对信息进行比较,比如可以通过某个人对一些事物的排名来估计他(或她)对各种不同信息的兴趣,从而实现个性化的服务. 对于不同的排名结果可以用逆序来评价它们之间的差异.考虑1,2,-,n的排列i1,i2,-,in,如果其中存在j,k,满足 j < k 且 ij > ik,那么就称(ij,ik)是这个排列的一个逆序. 一个排列含有逆序的个数称为这个排列的逆序数.例如排列 263451 含有8个逆序(2,1),(6,3),(6,4),(6,

二分&#183;归并排序与树状数组之逆序对 hiho1141

题目链接:二分·归并排序之逆序对 题目大意:N个整数,第i个数表示等级第i低的船的火力值a[i],求A船比B船等级高,但是A船火力低于B船,相当于就是求逆序数吧 解题思路: 把序列分成元素个数尽量相等的两半 把两半元素分别排序 把两个有序表合并成一个 二分归并排序做法: /************************************************************** Problem:hiho 1141 User: youmi Language: C++ Result

总结之---树状数组+逆序对问题。

咳咳,这个图必须要的.... 首先,当有一个数组a数量非常大的时候,我们可能改变某个a[i]的值,要求a[n]的和,全部加起来,无疑是要O(n)的时间复杂度. 但是如果n非常大时,O(n)时间复杂度肯定要跪,所以,怎么办的,用神奇的树状数组. 树状数组代码简单,但是非常强大!更令人兴奋的是,它的时间复杂度值需要O(logn)!!! 好了,首先要的东西是把上图的c[n]表示出来,该怎么弄呢,代码如下: int lowbit(int t) { return t&(-t); } 这个代码,简单到爆,但

Day2:T4求逆序对(树状数组+归并排序)

T4: 求逆序对 A[I]为前缀和 推导 (A[J]-A[I])/(J-I)>=M A[j]-A[I]>=M(J-I) A[J]-M*J>=A[I]-M*I 设B[]=A[]-M*(); B[J]>=B[I] 也就是求逆序对: 求逆序对的方法主要有两种: 归并排序: 树状数组: 这里两种方法都学习一下: 1.之前对于树状数组的印象就只有单点修改和区间求和 一直觉得lowbit是一个神奇的东西(至今没有搞懂原理) 上网搜了一下用树状数组求逆序对的方法,发现有一个大神写的很棒....看