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
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;
}