2020牛客寒假算法基础训练营day04 H-坐火车
思路
用树状数组维护([L,R])区间的答案,每次回答完后(O(logn))更新。
具体怎么做呢?
我们假设说目前考虑颜色(col),我左边有(x)个(col),右边有(num-x)个(col)。
那么(col)给答案的贡献就是(x*(num-x))。
假设我当前位置的颜色是(c),那么我考虑完我这个位置的时候,我往右边走一步,那么左边多一个(c),右边少一个(c)。
所以我只需要用树状数组维护左右每个颜色的乘积,每次修改两次(减去上一次的答案,添加新的答案)。
但是维护乘法爆(long long)了,我不知道为啥,我觉得按道理来说(5e5*5e5*5e5=1e17)不会炸。
如果有明白的朋友请联系我。
接下来我们看看怎么维护这样一个乘积。
假设说我在位置(i),颜色(c)左边有(x)个,右边有(sum-x)个。
此时的答案就是(x*(sum-x)=x*sum-x^2)。
继续遍历,到达位置(j),左边的颜色(c)有(x+1)个,右边有(sum-x-1)个。
此时的答案就是((x+1)*(sum-x-1)=x*sum-x^2-x+sum-x-1)。
所以我们只需要减去上次的(x),在加上一个(sum-x-1)就可以用加法代替乘法更新答案了。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5+10;
int n, col[maxn], r[maxn], l[maxn];
inline int lowbit(int x){return x&(-x);}
int mxx;
ll c[maxn];
inline void add(int x, ll y){
for(; x <= mxx; x += lowbit(x)) c[x] += y;
}
ll ask(int x){
ll ans = 0;
for(; x; x -= lowbit(x)) ans += c[x];
return ans;
}
ll vis[maxn];
ll used[maxn];
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
scanf("%d%d%d", &col[i], &l[i], &r[i]);
mxx = max(mxx, max(col[i], max(l[i], r[i])));
}
for(int i = 1; i <= n; i++) vis[col[i]]++;
ll ans = 0;
for(int i = 1; i <= n; i++)
{
int x = col[i];
add(x, -used[x]);
ans = ask(r[i]) - ask(l[i]-1);
used[x]++;
add(x, vis[x]-used[x]);
printf("%lld ", ans);
}
return 0;
}