【2019 Wannafly Winter Camp Day5 C】Division 题解

题目大意

  你有一个数列 $a_1,a_2,\cdots,a_n$。你可以进行这样的一次操作,每次选择数列中其中一个数然后将其除 $2$ 下取整,也就是选择一个数 $a_i$,变成 $\lfloor \frac{a_i}{2} \rfloor$。
  一共有 $q$ 个询问,每次你考虑数列中 $[l,r]$ 这段数,即 $a_l,a_{l+1},a_{l+2},\cdots,a_r$,对这些数字进行不超过 $k$ 次操作,这些数字的总和最小值可能是多少。

  $1 \leq n \leq 10^5,\ 1 \leq q \leq 5*10^5$
  $1 \leq a_i \leq 10^9,\ 0 \leq k \leq 10^9$
  5000 ms,256 MB

题解

  首先一个数字最多除 $\log$ 次,$\log$ 次以后就变成 $0$ 了。
  然后 $a_i$ 每除一次 $2$,就相当于减去一个 $\lceil \frac{a_i}{2} \rceil$。这样的话就可以把每个 $a_i$ 拆成 $\log$ 个数字之和。
  那么就相当于有 $n$ 个物品,每个物品可以拆成 $\log$ 个小物品,每个小物品有一个收益。然后每次询问一个区间,取 $k$ 个小物品,能获得的最大收益是多少。

  没啥特殊情况的话那就是区间前 $k$ 大和,主席树一下就行了。

  然后发现空间只有 256 MB

各凭本事卡内存
——杜老师

  题解的卡内存的姿势还是有丶巧妙的。

  题解的姿势就是,把这个主席树拆开来,拆成若干个小一点的主席树。
  怎么拆呢?就是把小物品按其价值的二进制位数分层,分成 $\log$ 层,每个大物品在每一层只会有一个小物品(每除一次有一个)。这样的话,每一层就只有 $n$ 个物品了,那么每一层的主席树就只有 $O(n \log a)$ 那么大了。
  然后求区间前 $k$ 大和,倒着枚举层(从高位到低位),每个询问只会在某一层处跑线段树二分,因此时间也还是 $O(q \log a)$ 的。

代码

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

typedef long long LL;

const int maxn=1e5+5, maxq=5e5+5, MX=31;

int n,q,maxw,maxx,l[maxq],r[maxq],c[maxq];
LL s[maxn];
vector<pair<int,int>> v[MX+5];

int root[maxn],tot,son[maxn*40][2],num[maxn*40];
LL sum[maxn*40];
int New()
{
tot++;
num[tot]=sum[tot]=son[tot][0]=son[tot][1]=0;
return tot;
}
int tr_xg(int k,int last,int l,int r,int x)
{
while (l<r)
{
sum[k]=sum[last]+x, num[k]=num[last]+1;
int t1=(l+r)>>1;
if (x<=t1)
{
son[k][0]=New();
son[k][1]=son[last][1];
k=son[k][0], last=son[last][0], r=t1;
} else
{
son[k][0]=son[last][0];
son[k][1]=New();
k=son[k][1], last=son[last][1], l=t1+1;
}
}
sum[k]=sum[last]+x, num[k]=num[last]+1;
}
LL tr_cx(int k,int last,int l,int r,int x)
{
LL re=0;
while (l<r)
{
int t1=(l+r)>>1;
if (num[son[k][1]]-num[son[last][1]]>=x) k=son[k][1], last=son[last][1], l=t1+1;
else
{
re+=sum[son[k][1]]-sum[son[last][1]];
x-=num[son[k][1]]-num[son[last][1]];
k=son[k][0], last=son[last][0], r=t1;
}
}
return (x) ?re+(sum[k]-sum[last])/(num[k]-num[last])*x :re ;
}

LL ans[maxq];
int main()
{
scanf("%d %d",&n,&q);
fo(i,1,n)
{
scanf("%lld",&s[i]);
maxx=(s[i]>maxx) ?s[i] :maxx ;
int w=0;
for(int x=s[i]; x; x>>=1) w++;
maxw=max(maxw,w-1);
for(int cnt=w-1, x=s[i]; x; cnt--, x>>=1) v[cnt].push_back(make_pair(i,x-(x>>1)));
s[i]+=s[i-1];
}
fo(i,1,q)
{
scanf("%d %d %d",&l[i],&r[i],&c[i]);
ans[i]=s[r[i]]-s[l[i]-1];
}

fd(i,maxw,0)
{
tot=-1;
int last=New();
memset(root,0,sizeof(root));
for(auto p:v[i])
{
tr_xg(root[p.first]=New(),last,1,maxx,p.second);
last=root[p.first];
}
fo(i,1,n) if (!root[i]) root[i]=root[i-1];

fo(j,1,q) if (c[j])
{
int cnt=num[root[r[j]]]-num[root[l[j]-1]];
if (cnt>=c[j])
{
ans[j]-=tr_cx(root[r[j]],root[l[j]-1],1,maxx,c[j]);
c[j]=0;
} else
{
c[j]-=cnt;
ans[j]-=sum[root[r[j]]]-sum[root[l[j]-1]];
}
}
}

fo(i,1,q) printf("%lld\n",ans[i]);
}