# G - Alone (atcoder.jp)
ABC 346 這一場來說相對比較簡單,F是一個細節比較多的二分,G也算是一個比較板子的題。
簡單說一下 G 題的思路。
其實比較容易想到用兩個陣列維護第 i 個數 \(a_i\) 在第 i 位之前出現的位置,以及第 i 個數在第 i 位之後出現的位置。那麼當前位的能夠滿足的區間就為 \((i - last_i) * (next_i - i)\)
但是每一位都這樣算會有重複,我們考慮一下如何去重。
對於第一組樣例:
5
2 2 1 2 1
我們發現相乘的本質實際上與分佈原理有一定關係,於是把對應的區間畫出來。
可以得到上圖,我們再聯絡一下,從兩個不同區間選兩個數的方案數等於兩個不同區間元素個數乘積,畫圖即矩形的面積。
會發現所有矩形的並的面積即是答案,不難想到掃描線演算法。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MAXN = 5e5+5;
ll X[MAXN], n, ans, a[MAXN];
ll l[MAXN];
ll last[MAXN], nxt[MAXN];
struct line{
ll x1, x2, y;
ll tag;
}L[MAXN];
bool operator < (line xx, line yy){
return xx.y < yy.y;
}
struct node{
ll l, r;
int cnt, len;
}tr[MAXN << 3];
void build(ll x, ll l, ll r){
tr[x] = {l, r, 0, 0};
if(l == r) return;
ll mid = l + r >> 1;
build(x << 1, l, mid);
build(x << 1 | 1, mid + 1, r);
}
void pushup(ll x){
ll l = tr[x].l, r = tr[x].r;
if(tr[x].cnt) tr[x].len = X[r + 1] - X[l];
else tr[x].len = tr[x << 1].len + tr[x << 1 | 1].len;
}
void modify(ll x, ll l, ll r, ll w){
if(tr[x].r < l || r < tr[x].l) return;
if(l <= tr[x].l && tr[x].r <= r){
tr[x].cnt += w;
pushup(x);
return;
}
modify(x << 1, l, r, w);
modify(x << 1 | 1, l, r, w);
pushup(x);
}
int main(){
ios::sync_with_stdio(false);
cin >> n;
for(int i = 1; i <= n; i ++){
cin >> a[i];
last[i] = l[a[i]];
nxt[l[a[i]]] = i;
nxt[i] = n + 1;
l[a[i]] = i;
}
for(int i = 1; i <= n; i ++){
L[i] = {last[i], i, i, 1};
L[n + i] = {last[i], i, nxt[i], -1};
X[i] = last[i];
X[n + i] = i;
}
n <<= 1;
sort(L + 1, L + n + 1);
sort(X + 1, X + n + 1);
int len = unique(X + 1, X + n + 1) - (X + 1);//去重 因為從 1開始所以減 x+1
build(1, 1, len - 1);
unordered_map<ll, ll> mp;
for(int i = 1; i <= len; i ++){
mp[X[i]] = i;
}
for(int i = 1; i < n; i ++){
modify(1, mp[L[i].x1], mp[L[i].x2] - 1, L[i].tag);
ans += tr[1].len * (L[i + 1].y - L[i].y);
// cout << L[i].y << endl;
}
cout << ans << endl;
return 0;
}