CF878E Numbers on the blackboard 题解
首先有一个很重要的观察:最后每一个 \(i\) 对答案的贡献为 \(a_i \times 2^{k_i}\),且 \(k\) 满足 \(k_1=0,k_{i,i>1} \ge 1,k_i \le k_{i-1}+1\)。
考虑贪心。若 \(a_i \ge 0\),则让 \(k_i=k_{i-1}+1\),否则让 \(k_i=0\)。也就是说我们把序列分成了一些段,每段的 \(k\) 都形如 \(1,2,\dots,x\)。
但是很显然这样的贪心是错的,比如 \(\{100,100,-1,100\}\)。最优解应该只分为一段,而这样贪心会分为两段。那如何改进呢。其实很简单,如果有一段的值是 \(\ge 0\) 的,那直接将这一段和前一段合并一定更优(这一段的系数变大),否则一定更劣。
怎么实现呢?我们可以先将询问离线下来并挂到右端点上。从左到右扫的过程中直接用并查集维护每一段,而 \([l,r]\) 只有最前面的一段可能不完整,这部分可以用类似哈希的做法求值。对于完整的几段可以用前缀和维护。
//dzzfjldyqqwsxdhrdhcyxll
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN = 1e5 + 10;
const int mod = 1e9 + 7;
typedef pair <int,int> pii;
vector <pii> v[MAXN];
int n,Q,a[MAXN],p[MAXN],h[MAXN],fa[MAXN]
int ans[MAXN],sum[MAXN],siz[MAXN],s[MAXN],val[MAXN];;
inline int Find(int x) {
if(x == fa[x]) return x;
return fa[x] = Find(fa[x]);
}
inline int Query(int l,int r) {
return (h[l] - h[r + 1] * p[r - l + 1] % mod + mod) % mod;
}
signed main() {
cin >> n >> Q;
for(int i = 1;i <= n;i++) cin >> a[i];
p[0] = 1;
for(int i = 1;i <= n;i++)
s[i] = a[i],siz[i] = 1,
val[i] = (a[i] % mod + mod) % mod,
p[i] = p[i - 1] * 2 % mod;
for(int i = n;i >= 1;i--)
h[i] = (h[i + 1] * 2 + a[i]) % mod;
for(int i = 1;i <= n;i++) fa[i] = i;
for(int i = 1,l,r;i <= Q;i++)
cin >> l >> r,
v[r].emplace_back(make_pair(l,i));
for(int i = 1;i <= n;i++) {
while(Find(i) != 1 && s[Find(i)] >= 0) {
int x = Find(i);
int y = Find(Find(i) - 1);
fa[x] = y;
if((s[x] != 0 && siz[y] > 40) || (s[y] + s[x] * p[siz[y]] > 1e10)) s[y] = 1e10;
else s[y] = s[y] + s[x] * p[siz[y]];
val[y] = (val[y] + val[x] * p[siz[y]] % mod) % mod;
siz[y] += siz[x];
}
sum[Find(i)] = (sum[Find(Find(i) - 1)] + val[Find(i)]) % mod;
for(pii j : v[i]) {
int l = j.first,id = j.second;
ans[id] = 2 * (sum[Find(i)] - sum[Find(l)] + mod) % mod;
ans[id] = (ans[id] + Query(l,Find(l) + siz[Find(l)] - 1)) % mod;
}
}
for(int i = 1;i <= Q;i++)
cout << (ans[i] + mod) % mod << endl;
return 0;
}