Codeforces Round #624 (Div. 3) F. Moving Points(樹狀數組+離散化)

在這裏插入圖片描述

題目大意

       數軸上有nn個點(2n21052\le n\le2·10^5),給出每個點的座標 xi(1xi108x_i (1\le x_i\le10^8)以及速度vi(108vi108)v_i(-10^8\le v_i\le 10^8),問在無窮時間範圍內,任意兩點間的最小距離之和是多少?(即求1i<jnd(i,j)\sum\limits_{1\le i<j\le n}d(i,j) 的值)

分析過程

       首先我們分析對於數軸上的兩個點xixjx_i和x_j(假設xi<xjx_i<x_j)來說:如果vi>vjv_i>v_j,那麼兩個點一定會在某一個時刻相遇;反之,兩點的初始位置之差便是兩點的最小距離。因此,對於任意一個點xix_i,我們只需要統計在它左側的速度比它小的點與它距離的絕對值之和即可。假設xix_i左側的速度比其小的位置爲x0,x1,x2,...,xjx_0,x_1,x_2,...,x_j,則我們所求即爲:(xix0)+(xix1)+(xix2)+...+(xixj)=mxi+p=0jxp(x_i-x_0)+(x_i-x_1)+(x_i-x _2)+...+(x_i-x_j)=m·x_i+\sum_{p=0}^jx_p(其中,mm爲比xix_i速度小的位置點數)
       所以我們其實只需要知道xix_i左側比它小的位置點的個數以及他們的和就可以了,這是一個典型的具備前綴特徵的情境。不難想到,我們可以用樹狀數組來解決這個問題。我們使用兩個樹狀數組,其中一個用於存儲viv_i的前綴和(以樹狀數組下標作爲值域,用於viv_i很大這裏需要離散化一下),另一個存儲對應點的位置,以便求出前綴和。
       綜上所述,本題做法如下:先對位置進行升序排序,然後離散化它們的viv_i,從左到右遍歷xx數組,每一次根據其速度對應的樹狀數組的前綴計算出結果並累加到ansans中,然後將該點也加入到樹狀數組中時間複雜度爲O(nlogn)O(nlogn)

AC代碼

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 200;
//bt[0]數組用於標記第i個位置之前比其速度小的座標個數
//bt[1]用於表示累計的座標前綴和 
ll n,arr[maxn],bt[2][maxn];
struct Point{
	ll x,v;
	bool operator()(Point a, Point b){
		return a.x < b.x;
	}
}p[maxn];
int low_bit(int x){
	return x & -x;
}
void add(int pos,int i,ll value){
	while(pos <= n){
		bt[i][pos] += value;
		pos += low_bit(pos);
	}
}
ll Query(int pos,int i){
	ll sum = 0;
	while(pos > 0){
		sum += bt[i][pos];
		pos -= low_bit(pos);
	} 
	return sum;
}
void preDeal(){
	int i;
	sort(arr+1,arr+1+n);
	int size = unique(arr+1,arr+1+n) - arr - 1;
	for(i=1;i<=n;++i){
		p[i].v = lower_bound(arr+1,arr+1+size,p[i].v) - arr;
	}
}
int main(){
	int i,j;
	ll ans = 0;
	ios::sync_with_stdio(false);
	cin>>n;
	for(i=1;i<=n;++i) cin>>p[i].x;
	for(i=1;i<=n;++i){
		cin>>p[i].v;
		arr[i] = p[i].v;
	}
	preDeal();
	sort(p+1,p+1+n,Point());
	for(i=1;i<=n;++i){
		ans += p[i].x * Query(p[i].v,0) - Query(p[i].v,1);
		add(p[i].v,0,1);
		add(p[i].v,1,p[i].x);
	}
	cout<<ans;
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章