【每日一题】30.储物点的距离 (区间处理,前缀和/线段树//树状数组)
补题链接:Here
算法涉及:前缀和,思维
一个数轴,每一个储物点会有一些东西,同时它们之间存在距离。
每次给个区间 \([l,r]\) ,查询把这个区间内所有储物点的东西运到另外一个储物点的代价是多少?
比如储物 \(i\) 点有\(x\) 个东西,要运到储物点 \(j\) ,代价为 \(x*dit(i,j)\)
dist(i, j) = abs(i - j)
就是储物点间的距离。
【解题思路】
对于一个区间 \([l, r]\) , 总的代价为 \(\sum\limits_{i = l}^{r}b[i] * abs(a[i] - a[x])\)
对于最终点 \(x\) , 我们可以分三种情况:
-
\(r\le x\),那么上面式子里的绝对值去掉得到 \(\sum\limits_{i = l}^{r}b[i] * abs(a[i] - a[x]) = a[x]\sum\limits_{i = l}^{r}b[i] - \sum\limits_{i = l}^{r}a[i]b[i]\)
-
\(x\le l\),那么上面式子里的绝对值去掉得到\(\sum\limits_{i = l}^{r}b[i] * abs(a[i] - a[x]) = -a[x]\sum\limits_{i = l}^{r}b[i] + \sum\limits_{i = l}^{r}a[i]b[i]\)
-
\(l < x < r\) , 那么对区间 \([l,x-1],[x,r]\) 分别做上述的操作即可
观察发现可以维护两个前缀和, \(\sum\limits_{i = l}^{r}a[i]b[i]\) 和 \(\sum\limits_{i = l}^{r}b[i]\) , 那么上述每次查询操作就能够 \(\mathcal{O}(1)\)
- 最后万分提醒,取模、取模再取模。。。Debug 超久
using ll = long long;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
ll a[N], b[N], c[N], sum[N];
void solve() {
int n, m;
cin >> n >> m;
for (int i = 2; i <= n; ++i) {
cin >> a[i]; // n - 1个数,第i个数表示第i个储物点与第i+1个储物点的距离ai
a[i] = (a[i - 1] + a[i]) % mod;
}
for (int i = 1; i <= n; ++i) {
cin >> b[i]; // 每个储物点的东西个数bi
c[i] = (a[i] * b[i]) % mod;
b[i] = (b[i] + b[i - 1]) % mod;
sum[i] = (sum[i - 1] + c[i]) % mod;
}
int l, r, x;
while (m--) {
cin >> x >> l >> r;
ll ans = 0;
if (x >= r) { // 情况 1
ans = a[x] * ((b[r] - b[l - 1] + mod) % mod) % mod;
ans = (ans - ((sum[r] - sum[l - 1] + mod) % mod) + mod) % mod;
ans = (ans + mod) % mod;
cout << ans << "\n";
} else if (l >= x) { // 情况 2
ans = (sum[r] - sum[l - 1] + mod) % mod;
ans = (ans - a[x] * ((b[r] - b[l - 1] + mod) % mod) + mod) % mod;
ans = (ans + mod) % mod;
cout << ans << "\n";
} else { // 情况 3
ll ans1 = (sum[r] - sum[x - 1] + mod) % mod;
ans1 = (ans1 - (a[x] * ((b[r] - b[x - 1]) + mod % mod)) + mod) % mod;
ans1 = (ans1 + mod) % mod;
ll ans2 = (a[x] * ((b[x - 1] - b[l - 1] + mod) % mod)) % mod;
ans2 = (ans2 - ((sum[x - 1] - sum[l - 1] + mod) % mod) + mod) % mod;
ans2 = (ans2 + mod) % mod;
cout << (ans1 + ans2) % mod << "\n";
}
}
}