权值线段树应用
普通的线段树的节点维护代表区间中存储的数组和,而权值线段树的节点是维护数组中出现在代表区间内的元素的次数。
权值线段树基本作用:查找数组中第k
大的元素
注意:权值线段树只是一个思想,一般需要根据题目对其意义加以改进
那么普通的线段树的叶节点 [x,x]
记录的就是数组中 a[x]
的大小,而权值线段树的叶节点 [x,x]
记录的就是数组中大小为 x
的元素个数
权值线段树模板
和线段树结构相同,只不过节点的权值记录的意义不相同而已。不过由于数组的值域,通常会采用现将数组离散化再建立对应的权值线段树来节省空间的白白浪费。当然还可以动态开点建树。
模板题1:在一个含有 1-n
的序列中,每次找到第 Ki
小的数,并把它删除。第一个数 T
,表示测试数据的组数。每组测试数据,第一行 2
个整数 n
和 m
, m
表示操作的轮数。接下来m
行,每行一个整数 k
,表示要找出第 k
小的数,并把它删除。每组数据,输出一个整数,表示删除元素的总和。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| #include<iostream> using namespace std; typedef long long ll; const int maxn=3e5+5;
int cnt=0; struct node{ int l,r,sum; }tree[maxn<<2];
void pushup(int rt){ tree[rt].sum=tree[rt<<1].sum+tree[rt<<1|1].sum; }
void build(int rt,int l,int r){ tree[rt].l=l,tree[rt].r=r; if(tree[rt].l==tree[rt].r){ tree[rt].sum=0; return ; } int mid=l+r>>1; build(rt<<1,l,mid); build(rt<<1|1,mid+1,r); pushup(rt); }
void update(int rt,int id,int score){ if(tree[rt].l==tree[rt].r){ tree[rt].sum+=score; return ; } if(id<=tree[rt<<1].r)update(rt<<1,id,score); else update(rt<<1|1,id,score); pushup(rt); }
int query(int rt,int k){ if(tree[rt].l==tree[rt].r)return tree[rt].l; int mid=tree[rt].l+tree[rt].r>>1; if(tree[rt<<1].sum>=k)return query(rt<<1,k); else return query(rt<<1|1,k-tree[rt<<1].sum); }
void solve(){ int n,m; cin>>n>>m;
build(1,1,n);
for(int i=1;i<=n;i++) update(1,i,1);
ll ans=0; while(m--){ int k;cin>>k; int tot=query(1,k); ans+=tot; update(1,tot,-1); } cout<<"Case "<<++cnt<<": "<<ans<<endl; }
signed main() { ios::sync_with_stdio(false),cin.tie(0); int t;cin>>t; while(t--){ solve(); } return 0; }
|
模板题2:给你一个序列对于每个 i
,你可以选择 1~i-1
中任意多的数并将它删去,剩余的数(包括 i
)和 ≤m
,问对于每个 i
最少删几个数可以达到要求
思路:可以转变思维即要求用在 1~i-1
的元素尽量多的个数去凑 m-a[i]
(小于等于尽量靠近)。现将数组离散化然后存入权值线段树,权值线段树既要统计区间内元素的个数还要统计其元素总和
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| #include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=2e5+5;
struct Tree{ int l,r,tot; ll sum; }tree[maxn<<2];
int n,m; int len,p; ll a[maxn],b[maxn];
void pushup(int rt){ tree[rt].sum=tree[rt<<1].sum+tree[rt<<1|1].sum; tree[rt].tot=tree[rt<<1].tot+tree[rt<<1|1].tot; }
void build(int rt,int l,int r){ tree[rt].l=l; tree[rt].r=r; tree[rt].tot=0; tree[rt].sum=0; if(l==r)return ; int mid=l+r>>1; build(rt<<1,l,mid); build(rt<<1|1,mid+1,r); pushup(rt); }
void update(int rt,int l,int r){ if(l==r){ tree[rt].tot++; tree[rt].sum+=b[p]; return ; } int mid=l+r>>1; if(p<=mid)update(rt<<1,l,mid); else update(rt<<1|1,mid+1,r); pushup(rt); }
int query(int rt,int l,int r,ll k){ if(tree[rt].sum<=k)return tree[rt].tot; if(l==r)return min(tree[rt].tot,(int)(k/b[l])); int mid=l+r>>1; if(k<=tree[rt<<1].sum)return query(rt<<1,l,mid,k); else return tree[rt<<1].tot+query(rt<<1|1,mid+1,r,k-tree[rt<<1].sum); }
void solve(){ cin>>n>>m; for(int i=1;i<=n;i++) cin>>a[i],b[i]=a[i];
sort(b+1,b+1+n); len=unique(b+1,b+1+n)-b-1; build(1,1,len);
for(int i=1;i<=n;i++){ if(i==1){ cout<<0<<" "; } else cout<<i-1-query(1,1,len,m-a[i])<<" "; p=lower_bound(b+1,b+1+n,a[i])-b; update(1,1,len); } cout<<endl; }
signed main() { ios::sync_with_stdio(false),cin.tie(0); int t;cin>>t; while(t--){ solve(); } return 0; }
|
权值线段树应用:求逆序对
树状数组都可以求逆序对,线段树作为另一种区间查询的数据结构当然也可以用来求逆序对
前言:逆序对是一个经典的问题,树状数组和权值线段树的求法都应该领悟
做法:和树状数组的方法类似,这里还是利用权值线段树区间统计个数的性质。思路是将离散化后的自然数序列中的数逐个插入(插入的数字所在的区间更新 +1 )。设插入的数字是原序列的第 i
个数:
- 可以插入前使用线段树查询
[a[i],n]
和(也就是相当于查询该数前面比它大的逆序对),然后再将该数插入线段树(利用线段树的性质还可这样做),即 sum+=query(1,a[i],n),update(1,a[i],1)
先查询再插入,因为是查找之前的数和该数能组成逆序对的,插入再查询就把自己给算进去了
- 或者先将数插入线段树,然后查询
[1,a[n]]
(也就是查询不满足逆序对的数),利用本身减去位置减去前面个数比它小的数和本身(树状数组的方法,线段树也可以实现),即 update(1,a[i],1),sum+=i-query(1,1,a[i])
先插入在查询,因为这里是找不能组成逆序对的数,所以要把自身算进去
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| #include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=5e3+5;
int a[maxn],b[maxn]; struct Tree{ int l,r,sum; }tree[maxn<<2];
void pushup(int rt){ int lc=rt<<1,rc=rt<<1|1; tree[rt].sum=tree[lc].sum+tree[rc].sum; }
void build(int rt,int l,int r){ tree[rt].l=l,tree[rt].r=r; if(l==r){ tree[rt].sum=0; return ; } int mid=(l+r)>>1; int lc=rt<<1,rc=rt<<1|1; build(lc,l,mid); build(rc,mid+1,r); pushup(rt); }
void update(int rt,int id,int score){ if(tree[rt].l==tree[rt].r){ tree[rt].sum+=score; return ; } int lc=rt<<1,rc=rt<<1|1; int mid=tree[rt].l+tree[rt].r>>1; if(id<=mid)update(lc,id,score); else update(rc,id,score); pushup(rt); }
int query(int rt,int l,int r){ if(l<=tree[rt].l&&r>=tree[rt].r)return tree[rt].sum; int lc=rt<<1,rc=rt<<1|1; int mid=tree[rt].l+tree[rt].r>>1; int ans=0; if(l<=mid)ans+=query(lc,l,r); if(r>mid)ans+=query(rc,l,r); return ans; }
signed main() { ios::sync_with_stdio(false),cin.tie(0); int n; while(cin>>n&&n){ memset(tree,0,sizeof tree); for(int i=1;i<=n;i++) cin>>a[i],b[i]=a[i];
sort(b+1,b+1+n); int len=unique(b+1,b+1+n)-b-1;
for(int i=1;i<=n;i++){ a[i]=lower_bound(b+1,b+1+len,a[i])-b; }
ll ans=0; build(1,1,len); for(int i=1;i<=len;i++){ ans+=query(1,a[i],len); update(1,a[i],1); } cout<<ans<<endl; } return 0; }
|
模板题:输入一个整数 n
,接下来随意顺序输入 0 到 n-1
之间的数,然后你可以将每一串这样的数的第一个数移到最后一位去,形成新的数字串。要求你输出这样的操作得到的不同数字串中逆序数的最小个数
如 0 3 4 1 2
,该序列逆序数为 n=2+2=4
。其根据题意移动产生的序列有(每次将数组第一个数移到末尾)
排列 |
逆序对 |
3 4 1 2 0 |
8 |
4 1 2 0 3 |
6 |
1 2 0 3 4 |
2 |
1 2 0 3 4 |
4 |
所以排列的最小逆序数为 2
思路:在寻找逆序对的基础上做了变式,要求求出 1~n
任意排列中的最小逆序对。这里有一个规律可以根据初始自然数排列列的逆序对找出最小逆序对:引用上面的例子
排列 |
逆序对 |
3 4 1 2 0 |
8 |
4 1 2 0 3 |
6 |
3 后面比 3 小的数有 1 2 0
,所以逆序对为 t=3
,比 3 大的数只有 4 ,所以顺序对为 1 ,即 顺序对=n-1-t(n表示序列中数字的总个数)
,当 3 移到末尾顺序对变为逆序对,逆序对变为顺序对,所以要在原来排列的逆序对 k
基础上减去该数原来的逆序对加上原来的顺序对,现在的逆序数=k+(n-1-t)-t=k+n-2t-1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| #include<bits/stdc++.h> using namespace std; const int maxn=5e3+5;
int n; int a[maxn]; int c[maxn];
int lowbit(int i){ return i&(-i); }
void update(int i,int k){ while(i<=n){ c[i]+=k; i+=lowbit(i); } }
int getsum(int i){ int res=0; while(i>0){ res+=c[i]; i-=lowbit(i); } return res; }
signed main(){ while(cin>>n&&n){ for(int i=1;i<=n;i++){ cin>>a[i]; a[i]++; } memset(c,0,sizeof(c)); long long tot=0; for(int i=1;i<=n;i++){ update(a[i],1); tot+=(i-getsum(a[i])); } long long ans=tot; for(int i=1;i<=n;i++){ tot=tot+n-2*a[i]+1; ans=min(tot,ans); } cout<<ans<<endl; } return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| #include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=5e3+5;
int n; int a[maxn]; struct Tree{ int l,r,sum; }tree[maxn<<2];
void pushup(int rt){ int lc=rt<<1,rc=rt<<1|1; tree[rt].sum=tree[lc].sum+tree[rc].sum; }
void build(int rt,int l,int r){ tree[rt].l=l,tree[rt].r=r; if(l==r){ tree[rt].sum=0; return ; } int mid=(l+r)>>1; int lc=rt<<1,rc=rt<<1|1; build(lc,l,mid); build(rc,mid+1,r); pushup(rt); }
void update(int rt,int id,int score){ if(tree[rt].l==tree[rt].r){ tree[rt].sum+=score; return ; } int lc=rt<<1,rc=rt<<1|1; int mid=tree[rt].l+tree[rt].r>>1; if(id<=mid)update(lc,id,score); else update(rc,id,score); pushup(rt); }
int query(int rt,int l,int r){ if(l<=tree[rt].l&&r>=tree[rt].r)return tree[rt].sum; int lc=rt<<1,rc=rt<<1|1; int mid=tree[rt].l+tree[rt].r>>1; int ans=0; if(l<=mid)ans+=query(lc,l,r); if(r>mid)ans+=query(rc,l,r); return ans; }
signed main() { ios::sync_with_stdio(false),cin.tie(0); while(cin>>n&&n){ memset(tree,0,sizeof tree); ll tot=0; build(1,1,n); for(int i=1;i<=n;i++){ cin>>a[i]; a[i]++; tot+=query(1,a[i],n); update(1,a[i],1); } ll ans=tot; for(int i=1;i<=n;i++){ tot=tot+n-2*a[i]+1; ans=min(ans,tot); } cout<<ans<<endl; } return 0; }
|