(有任何问题欢迎留言或私聊
这两天看了一下划分树的博客,推荐一个**大神博客,一看就懂。**
下面讲一下这两题题划分树的应用。
首先是HDU3473:
题目意思很裸,读完应该就知道xi应该就是中位数。
划分树建树复杂度时nlogn,m次查询,划分树每次查询是log复杂度。
所以要处理出来的就是怎么O(1)求出表达式的值。
解决方法就是把绝对值去掉,xi左边的比xi小,xi右边的比xi大,所以:
化简公式
sum = numLxi-sumL+0+sumR-numRxi
numL是在[LR]区间中小于xi的个数=mid-L
numR是在[LR]区间中大于xi的个数=R-mid
sumL是[LR]区间中小于xi的数的和
sumR是[LR]区间中大于xi的数的和
numL和numR很好求出来,问题在于如果枚举sumL的和复杂度是O(n),肯定吃不消。
为了把复杂度降低到O(log)可以用划分树求解。
在普通划分树中再加一个sum数组,sum[i]记录的是本段从开头带i进入左子树的权值之和。
具体看代码解释,十分详细。
AC代码:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cassert>
using namespace std;
const int N = 100005;
typedef long long LL;
/*
首先思考一下可只xi应该是中位数
我们要O1算出答案,算的时候xi在左边还是右边都行
把绝对值去掉就是: (numL*xi-sumL)+(sumR-numR*xi)
在划分树中加一个sum数组可以求出numL和sumL,再处理一个前缀和数组
sumL=sum;
sumR=pre[R]-pre[L-1]-sum;
numL=num;
numR=R-L+1-num;
mid=(R-L+2)>>1;
tmp=xi=query(mid);
sum=pre[y]-pre[x-1]-sum*2-(y-x+1-num-num)*tmp;
*/
struct lp{
int cnt[N];//本层中从本段左端点到i有多少个进入左子树
int num[N];//本层中从本段的数字排列
LL sum[N];本层中从1到i进入左子树的权值和
}cw[22];
LL pre[N],sum;
int n,m,num;
int sor[N];
int build(int l,int r,int d){
if(l==r)return cw[d].num[l];
cw[d].sum[0]=cw[d].cnt[0]=0;
int same=0,mid=(l+r)>>1;
for(int i=mid;i>=l;--i){//左边有多少的和mid值相等
if(sor[i]==sor[mid])same++;
else break;
}
int cnt=0;
int li=l,ri=mid+1;//下一层分段的起点,反正左右两边肯定是均分的(可能差1)
for(int i=l;i<=r;++i){
cw[d].sum[i]=cw[d].sum[i-1];//前缀进入左子树权值和
if(cw[d].num[i]<sor[mid]){//小于mid一律入左子树
cw[d+1].num[li++]=cw[d].num[i];
cw[d].sum[i]+=cw[d].num[i];
cnt++;
}else if(cw[d].num[i]==sor[mid]&&same){
same--;
cnt++;
cw[d+1].num[li++]=cw[d].num[i];
cw[d].sum[i]+=cw[d].num[i];
}else{
cw[d+1].num[ri++]=cw[d].num[i];
}
cw[d].cnt[i]=cnt;//统计本段到i进入左子树数量
}
build(l,mid,d+1);//递归
build(mid+1,r,d+1);
}
int query(int l,int r,int L,int R,int k,int d){
if(l==r)return cw[d].num[l];//如果只有一个数,直接返回
int left=0,sum_in_left=0,mid=(l+r)>>1;
if(L==l){
sum_in_left=cw[d].cnt[R];
}else{
sum_in_left=cw[d].cnt[R]-cw[d].cnt[L-1];//这是LR区间中进入左子树的数量
left=cw[d].cnt[L-1];//这是本段[l,L)区间中进入左子树的数量
}
int newl,newr;
if(sum_in_left>=k){//第k大数在左子树
newl=l+left;//L左边有left个进入左子树,过滤掉那些(不理解的话,取left为0理解
newr=l+left+sum_in_left-1;//下一段此区间有效的右边界
return query(l,mid,newl,newr,k,d+1);
}else{//在右子树
newl=mid+L-l+1-left;//过滤掉[l,L)中在右子树的数量
newr=mid+R-l+1-left-sum_in_left;//右边界(我把它化简了,不过很好推得
num+=sum_in_left;//把LR区间在左子树的数量累加
sum+=cw[d].sum[R]-cw[d].sum[L-1];//把LR区间在左子树权值累加
return query(mid+1,r,newl,newr,k-sum_in_left,d+1);
}
}
int main(int argc, char const *argv[]){
int tim;
scanf("%d",&tim);
for(int T=1;T<=tim;++T){
scanf("%d",&n);
pre[0]=0;
for(int i=1;i<=n;++i){
scanf("%d",&sor[i]);
cw[0].num[i]=sor[i];
pre[i]=pre[i-1]+sor[i];//区里出前缀和,方便O1求值
}
sort(sor+1,sor+1+n);//记得排序呀兄dei
build(1,n,0);
scanf("%d",&m);
printf("Case #%d:\n",T);
while(m--){
int x,y,z;
scanf("%d%d",&x,&y);
x++;y++;//要自加一下,因为我的下标从1开始
sum=0;num=0;//初始化
z=(y-x+2)>>1;//取中间一位
int mid=(x+y)>>1;
LL tmp=query(1,n,x,y,z,0);
assert(num==mid-x);
num=mid-x;
sum=pre[y]-pre[x-1]-sum*2-(y-x+1-num-num)*tmp;//推出来的公式
printf("%lld\n",sum );
}
printf("\n");//PE一发你就知道咯
}
return 0;
}
第二题CSU1080:
这题稍微难一点,但是把上面的代码改一下就可以ac了。本质是一样的,都是要log时间内求区间前k大的数和。
题意及处理:
中文题面没啥好说的,题目说的是每个学生最多解决k个任务。
毫无疑问,贡献为负数的任务不选
骚操作来了,负数贡献赋值为0,这样在用划分树处理前k大的数之和就十分方便了。
还要注意一点的是,题目给出的是难度值和贡献值对应一个下标,所以要离散化一下。离散化处理出每个学生能解决的难度对应的下标。
直接套用上面的代码,稍作修改就ac了。
AC代码:
//872ms
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cassert>
using namespace std;
const int N = 100005;
typedef long long LL;
struct lh{
int id,val;
}ar[N];
bool cmp1(lh &a,lh &b){
if(a.id!=b.id)return a.id<b.id;
return a.val<b.val;
}
bool cmp2(int i,int j){
return i<j;
}
struct lp{
int cnt[N];//本层中从本段左端点到i有多少个进入左子树
int num[N];//本层中从本段的数字排列
LL sum[N];本层中从1到i进入左子树的权值和
}cw[22];
int id[N],val[N];
int pre[N],sum;
int n,m,num;
int sor[N];
int build(int l,int r,int d){
if(l==r)return cw[d].num[l];
cw[d].sum[0]=cw[d].cnt[0]=0;
int same=0,mid=(l+r)>>1;
for(int i=mid;i>=l;--i){//左边有多少的和mid值相等
if(sor[i]==sor[mid])same++;
else break;
}
int cnt=0;
int li=l,ri=mid+1;//下一层分段的起点,反正左右两边肯定是均分的(可能差1)
for(int i=l;i<=r;++i){
cw[d].sum[i]=cw[d].sum[i-1];//前缀进入左子树权值和
if(cw[d].num[i]<sor[mid]){//小于mid一律入左子树
cw[d+1].num[li++]=cw[d].num[i];
cw[d].sum[i]+=cw[d].num[i];
cnt++;
}else if(cw[d].num[i]==sor[mid]&&same){
same--;
cnt++;
cw[d+1].num[li++]=cw[d].num[i];
cw[d].sum[i]+=cw[d].num[i];
}else{
cw[d+1].num[ri++]=cw[d].num[i];
}
cw[d].cnt[i]=cnt;//统计本段到i进入左子树数量
}
build(l,mid,d+1);//递归
build(mid+1,r,d+1);
}
int query(int l,int r,int L,int R,int k,int d){
if(l==r)return cw[d].num[l];//如果只有一个数,直接返回
int left=0,sum_in_left=0,mid=(l+r)>>1;
if(L==l){
sum_in_left=cw[d].cnt[R];
}else{
sum_in_left=cw[d].cnt[R]-cw[d].cnt[L-1];//这是LR区间中进入左子树的数量
left=cw[d].cnt[L-1];//这是本段[l,L)区间中进入左子树的数量
}
int newl,newr;
//printf("sum_in_left=%d\n",sum_in_left );
if(sum_in_left>=k){//第k大数在左子树
newl=l+left;//L左边有left个进入左子树,过滤掉那些(不理解的话,取left为0理解
newr=l+left+sum_in_left-1;//下一段此区间有效的右边界
return query(l,mid,newl,newr,k,d+1);
}else{//在右子树
newl=mid+L-l+1-left;//过滤掉[l,L)中在右子树的数量
newr=mid+R-l+1-left-sum_in_left;//右边界(我把它化简了,不过很好推得
//num+=sum_in_left;//把LR区间在左子树的数量累加
sum+=cw[d].sum[R]-cw[d].sum[L-1];//把LR区间在左子树权值累加
//printf("**%d\n",sum);
return query(mid+1,r,newl,newr,k-sum_in_left,d+1);
}
}
int main(int argc, char const *argv[]){
while(~scanf("%d",&n)){
for(int i=1;i<=n;++i){
scanf("%d%d",&ar[i].id,&ar[i].val);
if(ar[i].val<0)ar[i].val=0;
}
sort(ar+1,ar+1+n,cmp1);
pre[0]=0;
for(int i=1;i<=n;++i){
pre[i]=pre[i-1]+ar[i].val;//前缀和
id[i]=ar[i].id;//把难度值提出来,方便后面查询时得到下标
val[i]=ar[i].val;
sor[i]=val[i];
cw[0].num[i]=val[i];
}
sort(sor+1,sor+1+n);//记得排序呀兄dei
build(1,n,0);
scanf("%d",&m);
while(m--){
int x,y,z,s;
scanf("%d%d%d",&x,&y,&z);
x=lower_bound(id+1,id+1+n,x)-id;//得到下标
y=upper_bound(id+1,id+1+n,y)-id-1;
s=y-x+1-z;
//printf("*%d %d %d\n",x,y,s);
if(z>y-x+1){
printf("%d\n",pre[y]-pre[x-1]);
}else{
sum=0;
int tmp=query(1,n,x,y,s,0);
if(s)sum+=tmp;
tmp=pre[y]-pre[x-1]-sum;
printf("%d\n",tmp);
}
}
printf("\n");//PE一发你就知道咯
}
return 0;
}
主席树求动态区间前k大数的和
CCPC-Wannafly Winter Camp Day5 Div1 C题 Division 主席树
#include<bits/stdc++.h>
#define fi first
#define se second
#define iis std::ios::sync_with_stdio(false)
#define pb push_back
#define o2(x) (x)*(x)
using namespace std;
typedef long long LL;
typedef pair<int, LL> pii;
const int MXN = 1e6 + 6;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
int n, q;
int ar[MXN], num[MXN];
LL sum[MXN];
struct QUERY {
int l, r, k;
LL ans;
}cw[MXN];
struct lp {
int l, r, cnt;
LL sum;
}node[MXN*16];
int inde, Root[MXN];
void z_update(int old, int &cur, int val, LL L, LL R) {
cur = ++ inde;
node[cur] = node[old];
if(L == R) {
node[cur].sum += val - val/2;
++ node[cur].cnt;
return;
}
LL mid = (L + R) / 2;
if(val <= mid) z_update(node[old].l, node[cur].l, val, L, mid);
else z_update(node[old].r, node[cur].r, val, mid+1, R);
node[cur].sum = node[node[cur].l].sum + node[node[cur].r].sum;
node[cur].cnt = node[node[cur].l].cnt + node[node[cur].r].cnt;
}
LL z_query(int k, int old, int cur, LL L, LL R) {
if(L == R) {
return (LL)k*(L-L/2);//权值为L产生的贡献是k*(L-L/2)
}
LL mid = (L + R) / 2;
int tmp = node[node[cur].r].cnt - node[node[old].r].cnt;
if(k <= tmp) {
return z_query(k, node[old].r, node[cur].r, mid + 1, R);
}else {
return node[node[cur].r].sum - node[node[old].r].sum
+ z_query(k-tmp, node[old].l, node[cur].l, L, mid);
}
}
/*LL query(int k, int old, int cur, int L, LL R) {
printf("%d %lld %lld\n", k, node[cur].sum, node[old].sum);
if(L == R) {
printf("*%d %d\n", L,k*(L-L/2));
return (LL)k*(L-L/2);
}
LL mid = (L + R) / 2;
int tmp = node[node[cur].r].cnt - node[node[old].r].cnt;
printf("%d %d %d %d %lld\n", k, tmp, node[node[old].r].cnt,node[node[cur].r].cnt,node[node[cur].r].sum - node[node[old].r].sum);
if(k <= tmp) {
return query(k, node[old].r, node[cur].r, mid + 1, R);
}else {
return node[node[cur].r].sum - node[node[old].r].sum
+ query(k-tmp, node[old].l, node[cur].l, L, mid);
}
}*/
int main(){
scanf("%d%d", &n, &q);
for(int i = 1; i <= n; ++i) scanf("%d", ar+i), sum[i]=sum[i-1]+ar[i];
for(int i = 1; i <= q; ++i) {
scanf("%d%d%d", &cw[i].l, &cw[i].r, &cw[i].k);
cw[i].ans = sum[cw[i].r] - sum[cw[i].l-1];
}
LL L, R;
for(int T = 30; T >= 0; --T) {
L = 1LL<<T, R = 2LL<<T, inde = 0;
node[0].l = node[0].r = node[0].sum = node[0].cnt = 0;
for(int i = 1; i <= n; ++i) if(ar[i]>>T&1) {
z_update(Root[i-1], Root[i], ar[i], L, R);
//if(T == 2) printf("[%d %d]\n", i, ar[i]);
sum[i] = sum[i-1] + ar[i] - ar[i]/2; num[i] = num[i-1] + 1;
}else Root[i] = Root[i-1], sum[i] = sum[i-1], num[i] = num[i-1];
//printf("T = %d\n", T);
for(int i = 1, tmp; i <= q; ++i) {
if(cw[i].k == 0) continue;
if(cw[i].k >= num[cw[i].r] - num[cw[i].l-1]) {
cw[i].k -= num[cw[i].r] - num[cw[i].l-1];
cw[i].ans -= sum[cw[i].r] - sum[cw[i].l-1];
}else {
//printf("*%lld %d ", cw[i].ans, cw[i].k);
cw[i].ans -= z_query(cw[i].k, Root[cw[i].l-1], Root[cw[i].r], L, R);
//printf("%lld %d\n", cw[i].ans, T);
cw[i].k = 0;
}
//printf("%d %d %d\n", cw[i].l, cw[i].r, cw[i].k);
}
for(int i = 1; i <= n; ++i) if(ar[i]>>T&1) ar[i] >>= 1;
//printf("***\n");
}
for(int i = 1; i <= q; ++i) printf("%lld\n", cw[i].ans);
return 0;
}