[WC/CTS2024] 水镜

首先,这个 \(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;
}
posted @ 2024-03-19 21:54  Saltyfish6  阅读(6)  评论(0编辑  收藏  举报
Document