1. 程式人生 > 其它 >D. Reverse Sort Sum_線段樹維護差分

D. Reverse Sort Sum_線段樹維護差分

D. Reverse Sort Sum

題目大意:

有一個01串A。

定義f(k,A):將A的前k個數非降序排序所得到的串。現在有Bi=f(i,A)。現在有陣列C,其定義如下:
$$
C_j=\sum_{i = 1}^{i = n}B_{i,j}
$$
現在給出串C,要求回推初始串A。

思路和程式碼:

首先可以發現將C裡的元素累加起來除n可以得到一共有幾個1。

然後發現若最後一個元素等於其位置編號則其在A中一定是1。若這個是1,並且i及i之前一定是排好序的,所以所有的1都在i前面緊密排列。那麼我們就可以直接將i之前(包括i)的一個區間整體減去1。就是說,區間修改+單點查詢,可以用差分樹狀陣列,可以用懶標記,可以用差分線段樹。這裡我寫一個我比較熟的差分線段樹。

ll p[N] ;

struct Node{
	int l , r ;
	ll val ;
}tr[N << 2];

void pushup(Node &f , Node &l , Node &r){
	f.val = l.val + r.val ;
}

void build(int now , int l , int r){ 
	if(l == r){
		tr[now] = {l , r , p[l]} ;
		return ;
	}
	tr[now] = {l , r , 0 };
	int mid = l + r >> 1 ;
	build(now << 1 , l , mid) ;
	build(now << 1 | 1 , mid + 1 , r) ;
	pushup(tr[now] , tr[now << 1] , tr[now << 1 | 1]) ;
}

ll query(int now , int l , int r){ 
	//差分陣列單點查詢即字首和查詢
	if(l <= tr[now].l && tr[now].r <= r) return tr[now].val ;
	int mid = tr[now].l + tr[now].r >> 1 ;
	ll res = 0LL  ;
	if(l <= mid) res += query(now << 1 , l , r) ;
	if(mid < r ) res += query(now << 1 | 1 , l , r) ;
	return res ;
}

void modify(int now , int x , ll det){
	if(tr[now].l == tr[now].r && tr[now].l == x){
		tr[now].val += det ; return ;
	}
	int mid = tr[now].l + tr[now].r >> 1 ;
	if(x <= mid) modify(now << 1 , x , det) ;
	else modify(now << 1 | 1 , x , det) ;
	pushup(tr[now] , tr[now << 1] , tr[now << 1 | 1]) ;
}

void solve(){
	cin >> n ;
	vct<ll> a(n + 1 , 0) ; 
	vct<ll> ans(n + 1 , 0) ; 
	
	rep(i , 1 , n) cin >> a[i] ;
	
	rep(i , 1 , n) p[i] = a[i] - a[i - 1] ;
	
	build(1 , 1 , n) ;
	
	ll k = accumulate(a.begin() + 1 , a.end() , 0LL) / (1LL * n) ;
	
	drep(i , 1 , n){
		int pre = i - k + 1 ;
		ll now = query(1 , 1 , i) ;
		if(!now) continue ;
		if(now == i){
			ans[i] = 1 ;
			k -- ;
		}
		
		modify(1 , pre , -1) ;
		if(i + 1 <= n) modify(1 , i + 1 , 1) ;
		
	}
	
	rep(i , 1 , n) cout << ans[i] << " \n"[i == n] ;
	
}//code_by_tyrii 
小結:

這題的關鍵就是f(k,A)的規律。即在[1,k]區間中是[00...111]這樣的。就是說當前剩餘的1的數量對整體的影響的只集中於[i-k+1,i]這一段中。所以可以用區間修改來做。