【2019 Wannafly Winter Camp Day5 C】Division 题解

题目大意

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

  1n105, 1q51051 \leq n \leq 10^5,\ 1 \leq q \leq 5*10^5
  1ai109, 0k1091 \leq a_i \leq 10^9,\ 0 \leq k \leq 10^9
  5000 ms,256 MB

题解

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

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

  然后发现空间只有 256 MB

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

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

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