[ABC221E] LEQ 題解

2020luke發表於2024-04-04

[ABC221E] LEQ 題解

思路解析

很有思維量的一道題。首先根據題目要求發現,新求的子序列只跟子序列的頭尾有關,而在確定頭尾之後中間的元素選或不選沒有任何關係。也就是確定新子序列的頭尾下標分別為 \(i,j\),那麼以當前頭尾的可行子序列個數就是 \(2^{j-i-1}=2^j \div 2^{i+1}\) 種可能。

接下來我們思考,根據題目要求,上方的 \(i,j\) 一定有 \(a_i \le a_j\),於是我們要做的其實就是找固定 \(j\),有多少個 \(i\) 使得 \(i<j,a_i \le a_j\),也就是個二維偏序。但事實上不需要那麼複雜,假設有 \(i_1,i_2,\dots,i_k\) 都滿足題目的條件,那麼以 \(j\) 結尾的子序列的個數就是 \(2^j \div 2^{i_1+1}+2^j \div 2^{i_2+1}+\dots+2^j \div 2^{i_k+1}=2^j \div (2^{i_1+1}+2^{i_2+1}+\dots+2^{i_k+1})\)

那麼接下來就很輕鬆了,由於上方的 \(i,j\) 一定有 \(a_i \le a_j\),所以我們可以用一個 \(rk_i\) 存下 \(a_i\) 的排名,設 \(v_{rk_i}=2^{i+1}\),然後求 \(\sum_{k=1}^{rk_j-1}v_k\),這樣我們就可以求得滿足 \(a_i \le a_j\) 的條件所有的 \(i\) 對答案的貢獻,還要繼續考慮 \(i<j\) 這個條件。我們其實可以不先處理出來 \(v\) 的值,而是在 \(j \to j+1\) 時處理 \(v_j\) 的值,這樣就能保證所有 \(v\) 當中有值的 \(v_{rk_i}\) 都有 \(i<j\)。由於 \(v\) 需要滿足單點修改和區間求和的操作,所以需要使用樹狀陣列最佳化。

注意:由於有取模和乘方,所以需要使用逆元和快速冪求解。

時間複雜度:需要遍歷每個下標,都需要一次區間查詢和單點修改,複雜度為 \(O(n \log n)\)

code

//ABC211E
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 3e5 + 10;
const ll mod = 998244353;
int n, m;
ll a[N], c[N], b[N];
map<ll, int> rk;

ll ksm(ll a, ll b, ll p) {
	ll ans = 1;
	while(b) {
		if(b & 1) {
			ans = ans * a % p;
		}
		a = a * a % p;
		b >>= 1;
	}
	return ans;
}
ll niyuan(ll a, ll p) {
	return ksm(a, p - 2, p);
}

void add(ll x, ll y) {
	for(; x <= n; x += (x & -x)) {
		c[x] = (c[x] + y) % mod;
	}
}
ll ask(ll x) {
	ll sum = 0;
	for(; x > 0; x -= (x & -x)) {
		sum = (sum + c[x]) % mod;
	}
	return sum;
}

int main() {
	cin >> n;
	for(int i = 1; i <= n; i++) {
		cin >> a[i];
		b[i] = a[i];
	}
	sort(b + 1, b + n + 1);
	for(int i = 1; i <= n; i++) {
		rk[b[i]] = i;
	}
	ll ans = 0;
	for(int i = 1; i <= n; i++) {
		ans = (ans + (ksm(2, i, mod) * ask(rk[a[i]])) % mod) % mod;
		add(rk[a[i]], ksm(niyuan(2, mod), i + 1, mod));
	}
	cout << ans;
	return 0;
}