[WC/CTS2024] 水鏡

Saltyfish6發表於2024-03-19

首先,這個 \(2L\) 看著很煩,下文就定義為 \(L\)

我們仔細觀察這個題目,可以發現,如果 \(h_i\le h_{i-1}\),那麼我們為了使其滿足條件,就必須要讓一下條件至少滿足其一:

  • \(L-h_i> h_{i-1}\)

  • \(h_i> L-h_{i-1}\)

本質上就是要麼滿足 \(L< h_i+h_{i-1}\) 要麼滿足 \(L> h_i+h_{i-1}\),而且可以發現,如果 \(i\) 選擇了其中之一,那麼對於 \(i+1\),且 \(h_{i+1}\le h_i\),那就只能滿足另外一個。

於是這個問題就越來越像一個 \(01\) 串了,而且似乎如果我們要將兩個區間的答案合併起來,只需要知道端點的 \(01\) 情況就可以了。

那就顯然想到線段樹了。

我們對於線段樹上節點,維護這個節點對應區間端點 \(l,r\),是選擇的 \(a_r\) 還是 \(L-a_r\),是選擇的 \(a_{l-1}\) 還是 \(L-a_{l-1}\)\(L\) 在什麼區間內,才能滿足 \(a'_r>a'_{l-1}\)。就是維護一個 \(lres[0/1][0/1]\)\(rres[0/1][0/1]\)。(當然,要同時滿足中間也是合法的情況)

然後,我們考慮葉子節點怎麼求解。假設其對應下標為 \(x\),那麼我們就把每種 \(01\) 狀態拿出來暴力討論即可得到 \(L\) 的取值範圍。

接著,考慮怎麼合併左右兒子。其實很簡單,我們先列舉當前節點左右端點的 \(01\) 狀態 \(i,j\),然後我們考慮中間 \(mid\) 的狀態。由於我們在他的右兒子已經考慮到了 \((mid+1)-1\),所以左兒子 \(r\) 的狀態必須與右兒子 \(l-1\) 的狀態相同,所以轉移如下:

node merge(node &x, node &y)
{
	node res;
	res[0][0] = get2(get1(x[0][0], y[0][0]), get1(x[0][1], y[1][0]));
	res[0][1] = get2(get1(x[0][0], y[0][1]), get1(x[0][1], y[1][1]));
	res[1][0] = get2(get1(x[1][0], y[0][0]), get1(x[1][1], y[1][0]));
	res[1][1] = get2(get1(x[1][0], y[0][1]), get1(x[1][1], y[1][1]));
	return res;
}

其中 \(\texttt{get2}\) 是求並集,\(\texttt{get1}\) 是求交集。

這也是為什麼我們取 \(l-1\) 的精妙之處所在。當然,建樹的時候就不要忘了是 \(\texttt{build(2,n)}\)

於是,我們就可以透過此方法建樹,並透過查詢在 \(\mathcal{O}(\log n)\) 得到任意一個區間是否合法(就是看最後得到的區間是否真的是個合法的區間)。

有一個顯然的思路是列舉 \(v\),然後二分 \(u\in[1,i-1]\),然後看是否合法在二分中進行調整即可。

這個單調性和正確性顯然,就不細說,複雜度 \(\mathcal{O}(n\log^2 n)\),還有一些 \(01\) 串帶來的巨大常數,如果實現精細,應該可以在考場的好機子上得到 \(\texttt{80pts}\) 的好成績。

我們還是覺得我們邊二分一次邊查詢一下太慢了,所以考慮把這個二分直接搬到線段樹上。

首先,我們先把 \([2,i]\) 在樹上對應的節點給拿下來。然後我們按照區間右端點從大到小進行合併,如果合併著合併著突然發現不合法了,那麼就在這個節點對應的子樹上進行樹上二分,找到最小的那個合法的點即可。

所以這個時候,首先取區間複雜度 \(\mathcal{O}(n\log n)\),然後樹上二分,由於我們每次只需要判斷當前狀態與右兒子合併之後是否合法來判斷答案是在左兒子還是在右兒子上,複雜度仍然為 \(\mathcal{O}(n\log n)\)

最後注意一個細節,就是這個 \(\texttt{merge}\) 他不滿足交換律,一定要注意 \(\texttt{merge}\) 的順序。

寫這道題的意義在於,如果我們要求一個區間的值,但是又有一些 \(0/1\) 合法的狀態在裡面,顯然那可以用一個 \(\texttt{dp}\) 做到 \(\mathcal{O}(r-l+1)\),進一步最佳化,最先想到矩陣快速冪,但是顯然不合理,所以就進一步想到線段樹的兩個區間合併,討論一下端點情況即可。以及注意線段樹端點取值的精妙性。

程式碼

#include <bits/stdc++.h>
using namespace std;
#define maxn 500005
#define ll long long
inline char gc()
{
    static char buf[100010], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100010, stdin), p1==p2) ? EOF : *p1++;
}
template<typename T> inline void read(T&x)
{
    x = 0;
    bool f=0;
    static char s = gc();
    while(s < '0' || s > '9') f |= s=='-', s = gc();
    while(s >= '0' && s <= '9') x = (x << 3) + (x << 1) + (s ^ 48), s = gc();
    if(f) x = -x;
}
const long long inf = 1e18;
int n;
ll a[maxn];
pair<ll, ll> get1(pair<ll, ll> x, pair<ll, ll> y)//算交集 
{
	return make_pair(max(x.first, y.first), min(x.second, y.second));
}
pair<ll, ll> get2(pair<ll, ll> x, pair<ll, ll> y)//算並集 
{
	return make_pair(min(x.first, y.first), max(x.second, y.second));
}
struct node
{
	pair<ll, ll> res[2][2];
	node operator +(const node &n)const
	{
		node now;
		now.res[0][0] = get2(get1(res[0][0], n.res[0][0]), get1(res[0][1], n.res[1][0]));
		now.res[0][1] = get2(get1(res[0][0], n.res[0][1]), get1(res[0][1], n.res[1][1]));
		now.res[1][0] = get2(get1(res[1][0], n.res[0][0]), get1(res[1][1], n.res[1][0]));
		now.res[1][1] = get2(get1(res[1][0], n.res[0][1]), get1(res[1][1], n.res[1][1]));
		return now; 
	} 
}tree[maxn << 2];
void build(int p, int l, int r)
{
	if(l == r)
	{
		ll sum = a[l] + a[l - 1];
		tree[p].res[0][1] = make_pair(sum, inf), tree[p].res[1][0] = make_pair(0, sum);
		if(a[l] < a[l - 1]) tree[p].res[0][0] = make_pair(inf, 0), tree[p].res[1][1] = make_pair(0, inf);
		else if(a[l] == a[l - 1]) tree[p].res[0][0] = tree[p].res[1][1] = make_pair(inf, 0);
		else tree[p].res[0][0] = make_pair(0, inf), tree[p].res[1][1] = make_pair(inf, 0);
		return;
	}
	int mid = (l + r) >> 1;
	build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r);
	tree[p] = tree[p << 1] + tree[p << 1 | 1];
}
vector<int> q;
vector<pair<int, int> > qwq;
void get_query(int p, int l, int r, int L, int R)
{
	if(L > r || R < l) return;
	if(L >= l && R <= r) return q.push_back(p), qwq.push_back(make_pair(L, R)), void(0);
	int mid = (L + R) >> 1;
	get_query(p << 1, l, r, L, mid), get_query(p << 1 | 1, l, r, mid + 1, R);
}
int query(int p, int L, int R, node now)
{
	if(L == R) return L;
	int mid = (L + R) >> 1;
	node Now = tree[p << 1 | 1] + now;
	if(Now.res[0][0].first >= Now.res[0][0].second && Now.res[0][1].first >= Now.res[0][1].second
	&& Now.res[1][0].first >= Now.res[1][0].second && Now.res[1][1].first >= Now.res[1][1].second)
	return query(p << 1 | 1, mid + 1, R, now);
	else
	return query(p << 1, L, mid, Now);
}
int main()
{
	read(n);
	for (int i = 1; i <= n; ++i) read(a[i]);
	build(1, 2, n);
	long long sum = 0;
	for (int i = 2; i <= n; ++i)
	{
		q.clear(), qwq.clear();
		get_query(1, 2, i, 2, n);
		node now;
		now.res[0][0] = now.res[0][1] = make_pair(0, inf);
		now.res[1][0] = now.res[1][1] = make_pair(0, inf);
		int ans = 1;
		for (int j = q.size() - 1; j >= 0; --j)
		{
//			cout << i << " " << qwq[j].first << " " << qwq[j].second << " " << ans << endl;
			node Now = now;
			now = tree[q[j]] + now;
			if(now.res[0][0].first >= now.res[0][0].second && now.res[0][1].first >= now.res[0][1].second
			&& now.res[1][0].first >= now.res[1][0].second && now.res[1][1].first >= now.res[1][1].second)
			{
				ans = query(q[j], qwq[j].first, qwq[j].second, Now);
//				cout << i << " " << qwq[j].first << " " << qwq[j].second << " " << ans << endl;
				break;
			}
		}
		sum += i - ans;
	}
	cout << sum << endl;
	return 0;
}

相關文章