CF477E Dreamoon and Notepad

发布时间 2023-08-31 22:49:32作者: Schucking_Sattin

CF477E Dreamoon and Notepad

key:分类讨论;贪心;拆贡献、放缩等思想。

Solution

简要 写一下分类讨论,防止写代码的时候脑子不清晰。参考了 这篇 题解。

首先可以发现一个关键性质:HOMEEND 在一次询问中不能同时使用,并且 HOMEEND 最多只会使用一次,因为只会保留二者的所有操作集合中的最后一次使用。

所以有了第一个分类依据:1.只使用上下左右移动;2.使用 HOME;3.使用 END

第二个分类依据是纵向移动的方式:1.不改变纵向方向;2.向上绕行;3.向下绕行。

为了方便处理,对于一次 \((r_1, c_1) \to (r_2, c_2)\) 的移动,我们假定 \(r_1 \le r_2\)。否则,我们可以让序列翻转,归约到 \(r_1 \le r_2\) 的问题。

以下的讨论建立在一个非常重要的贪心策略上,那就是只会在结束行使用左右移动。证明略。

除此之外,各类讨论均涉及到一些贪心,大都比较简单,证明略。

不改变纵向方向

  • 既不使用 HOME,也不使用 END

    步数:\(r_2 - r_1 + |c_2 - \min(c_1, w[r_1, r_2])|\)。此处及下文 \(w[l, r]\) 均表示 \(\min\limits_{i = l}^{r}a_i\)

  • 使用 HOME

    你在哪一行使用 HOME 都可以,但只在第 \(r_2\) 行使用右移。

    步数:\(r_2 - r_1 + c_2 + 1\)

  • 使用 END

    你并不清楚在哪一行使用 END 更优,考虑枚举在哪一行使用 END。假设为 \(r_0\)

    则步数为:\(r_2 - r_1 + |c_2 - w[r_0, r_2]| + 1\)。枚举 \(r_0\) 不现实,由于 \(w[l, r]\)\(r\) 固定时关于 \(l\) 单增,考虑强行拆绝对值。

    • \(c_2 \le w[r_0, r_2]\):需要让二者差值尽量小,则后者尽量小,则 \(r_0\) 尽量小,可二分/倍增出最优的 \(r_0\),更新答案。

    • \(c_2 > w[r_0, r_2]\):需让后者尽量大,则 \(r_0\) 尽量大,可直接利用上一种情况的 \(r_0\) 减一。更新答案。注意 \(w[r_0, r_2] = a_{r_0}\),所以没必要在 ST 表上查。以下存在这种优化的讨论将不再提及此优化。

向上绕行

向上绕行使用 HOME 一定不优。

  • 既不使用 HOME,也不使用 END

    假设向上最远到第 \(r_0(r_0 \le r_1)\) 行。

    步数:\(r_1 - r_0 + r_2 - r_0 + |c_2 - \min(c_1, w[r_0, r_2])|\)

    其中 \(w[r_0, r_2] = \min(w[r_0, r_1], w[r_1, r_2])\),后者为定值,提出来与 \(c_1\) 合在一起,记为 \(c_0 = \min(c_1, w[r_1, r_2])\)

    步数:\(r_1 + r_2 - 2r_0 + |c_2 - \min(c_0, w[r_0, r_1])|\)

    \(c_0 \le c_2\),则向上绕行只会使 \(\min(c_0, w[r_0, r_1])\) 减小,从而使步数变大,因此此时向上绕行一定不优。

    所以只在 \(c_0 > c_2\) 时考虑向上绕行。同时只有 \(w[r_0, r_1] < c_0\) 时,\(\min(c_0, w[r_0, r_1])\) 才会变化,向上绕行才会有意义,步数为:\(r_1 + r_2 - 2r_0 + |c_2 - w[r_0, r_1]|\)

    • \(c_2 \le w[r_0, r_1]\):问题变为最小化 \(w[r_0, r_1] - 2r_0\)。利用 放缩 思想,构建 \(a_x - 2x\) 的 ST 表,在满足 \(w[r_0, r_1] \ge c_2\) 的区间上查最小值即可。
    • \(c_2 > w[r_0, r_1]\):要最小化 \(-(w[r_0, r_1] + 2r_0)\)。该式关于 \(r_0\) 单减,因此找到满足条件的最大 \(r_0\) 进行更新即可。

    由于 \(a_{r_1} \ge c_0 > c_2\),所以 \(c_2 \le w[r_0, r_1]\) 时的二分一定有解。

  • 使用 END

    贪心策略是,向上最远到第 \(r_0\) 行,并在该行使用 END,然后往回走。

    步数:\(r_1 - r_0 + r_2 - r_0 + |c_2 - w[r_0, r_2]| + 1\)。可以按第一大类中第三小类的方法分类讨论:

    • \(c_2 \le w[r_0, r_2]\):要最小化 \(w[r_0, r_2] - 2r_0\)。这与该大类的第一类求法完全一样,二分出对应区间后在 \(a_x - 2x\) 的 ST 表上查即可。(该情况存在在 ST 表上查出来与原式不符,但可以证明这些情况向上绕行一定不优)
    • \(c_2 > w[r_0, r_2]\):要最小化 \(-(w[r_0, r_2] + 2r_0)\)。该式关于 \(r_0\) 单减,找到满足条件的最大 \(r_0\) 即可。

    如果 \(w[r_1, r_2] < c_2\),则 \(c_2 \le w[r_0, r_2]\) 时二分无解,但舒服的地方在于,如果是这样的,则向上绕行一定不优。

向下绕行

向下绕行使用 HOME 一定不优。

记向下最远到达 \(r_0(r_0 \ge r_2)\)。由于求法与第二大类一致,因此只是简略地写一下式子。

  • 既不使用 HOME,也不使用 END

    步数 \(2r_0 - r_1 - r_2 + |c_2 - \min(c_1, w[r_1, r_0])|\)

    \(w[r_1, r_0] = \min(w[r_1, r_2], w[r_2, r_0])\)

    \(c_0 = \min(c_1, w[r_1, r_2])\),步数:\(2r_0 - r_1 - r_2 + |c_2 - \min(c_0, w[r_2, r_0])|\)

    只有 \(c_0 > c_2\)\(c_0 > w[r_2, r_0]\) 时,该情况可能更新答案。步数为:\(2r_0 - r_1 - r_2 + |c_2 - w[r_2, r_0]|\)

    • \(c_2 \le w[r_2, r_0]\) 时:要最小化 \(w[r_2, r_0] + 2r_0\),构建 \(a_x + 2x\) 的 ST 表进行查询。
    • \(c_2 > w[r_2, r_0]\) 时:要最小化 \(2r_0 - w[r_2, r_0]\),该式关于 \(r_0\) 单增,用最小的 \(r_0\) 更新即可。
  • 使用 END

    步数:\(2r_0 - r_1 - r_2 + |c_2 - w[r_2, r_0]| + 1\)

    • \(c_2 \le w[r_2, r_0]\):要最小化 \(w[r_2, r_0] + 2r_0\)。利用 \(a_x + 2x\) 的 ST 表可得。
    • \(c_2 > w[r_2, r_0]\):要最小化 \(2r_0 - w[r_2, r_0]\)。该式关于 \(r_0\) 单增,用最小的 \(r_0\) 更新答案即可。

实现上有一些取等之类的细节与上文不同(主要是方便二分,让二分区间内一定有解),但并无大碍。

#include<bits/stdc++.h>
#define LL long long
#define DB double
#define MOD 1000000007
#define ls(x) x << 1
#define rs(x) x << 1 | 1
#define lowbit(x) x & (-x)
#define PII pair<int, int>
#define MP make_pair
#define VI vector<int>
#define VII vector<int>::iterator
#define all(x) x.begin(), x.end()
#define EB emplace_back
#define SI set<int>
#define SII set<int>::iterator
#define QI queue<int>
using namespace std;
template<typename T> void chkmn(T &a, const T &b) { (a > b) && (a = b); }
template<typename T> void chkmx(T &a, const T &b) { (a < b) && (a = b); }
int inc(const int &a, const int &b) { return a + b >= MOD ? a + b - MOD : a + b; }
int dec(const int &a, const int &b) { return a - b < 0 ? a - b + MOD : a - b; }
int mul(const int &a, const int &b) { return 1LL * a * b % MOD; }
int sqr(const int &a) { return 1LL * a * a % MOD; }
void Inc(int &a, const int &b) { ((a += b) >= MOD) && (a -= MOD); }
void Dec(int &a, const int &b) { ((a -= b) < 0) && (a += MOD); }
void Mul(int &a, const int &b) { a = 1LL * a * b % MOD; }
void Sqr(int &a) { a = 1LL * a * a % MOD; }
int qwqmi(int x, int k = MOD - 2)
{
	int res = 1;
	while(k)
	{
		if(k & 1) Mul(res, x);
		Sqr(x), k >>= 1;
	}
	return res;
}
void read(int &x)
{
	x = 0;
	int f = 1;
	char ch = getchar();
	while(!isdigit(ch))
	{
		if(ch == '-')
			f = -1;
		ch = getchar();
	}
	while(isdigit(ch))
	{
		x = (x << 1) + (x << 3) + (ch ^ 48);
		ch = getchar();
	}
	x = x * f;
}
const int N = 4e5 + 5;
const int F = 21;
const int INF = 1e9 + 7;
struct Akashi
{
	int r1, c1, r2, c2, id;
}aks1[N], aks2[N];
int n, Q, a[N], cnt1, cnt2, ans[N];
struct ST
{
	int f[N][F];
	void build()
	{
		for(int j = 1; j < F; ++j)
			for(int i = 1; i + (1 << j) - 1 <= n; ++i)
				f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]);
	}
	int query(int l, int r)
	{
		int j = log2(r - l + 1);
		return min(f[l][j], f[r - (1 << j) + 1][j]);
	}
}st1, st2, st3;
void build()
{
	for(int i = 1; i <= n; ++i)
	{
		st1.f[i][0] = a[i];
		st2.f[i][0] = a[i] - 2 * i;
		st3.f[i][0] = a[i] + 2 * i;
	}
	st1.build();
	st2.build();
	st3.build();
}
int solve(int r1, int c1, int r2, int c2)
{
	int res = INF, l, r, mid;
	int r0, c0 = min(c1, st1.query(r1, r2));

	// situation 1
	chkmn(res, r2 - r1 + abs(c2 - c0));
	chkmn(res, r2 - r1 + c2 + 1);
	l = r1, r = r2;
	while(l <= r)
	{
		mid = (l + r) / 2;
		if(st1.query(mid, r2) >= c2) 
			r = mid - 1, r0 = mid;
		else l = mid + 1;
	};
	chkmn(res, r2 - r1 + st1.query(r0, r2) - c2 + 1);
	if((--r0) >= r1) chkmn(res, r2 - r1 + c2 - a[r0] + 1);

	// situation 2
	if(c0 > c2)
	{
		l = 1, r = r1;
		while(l <= r)
		{
			mid = (l + r) / 2;
			if(st1.query(mid, r1) >= c2)
				r = mid - 1, r0 = mid;
			else l = mid + 1;
		};
		chkmn(res, r1 + r2 - c2 + st2.query(r0, r1));
		if((--r0) >= 1) chkmn(res, r1 + r2 + c2 - 2 * r0 - a[r0]);
	}
	if(st1.query(r1, r2) >= c2)
	{	
		l = 1, r = r1;
		while(l <= r)
		{
			mid = (l + r) / 2;
			if(st1.query(mid, r2) >= c2)
				r = mid - 1, r0 = mid;
			else l = mid + 1;
		};
		chkmn(res, r1 + r2 - c2 + st2.query(r0, r1) + 1);
		if((--r0) >= 1) chkmn(res, r1 + r2 + c2 - 2 * r0 - a[r0] + 1);
	}

	// situation 3
	if(c0 > c2)
	{
		l = r2, r = n;
		while(l <= r)
		{
			mid = (l + r) / 2;
			if(st1.query(r2, mid) >= c2)
				l = mid + 1, r0 = mid;
			else r = mid - 1;
		};
		chkmn(res, st3.query(r2, r0) - (r1 + r2 + c2));
		if((++r0) <= n) chkmn(res, c2 - r1 - r2 + 2 * r0 - a[r0]);
	}
	l = r2, r = n;
	while(l <= r)
	{
		mid = (l + r) / 2;
		if(st1.query(r2, mid) >= c2)
			l = mid + 1, r0 = mid;
		else r = mid - 1;
	};
	chkmn(res, st3.query(r2, r0) - (r1 + r2 + c2) + 1);
	if((++r0) <= n) chkmn(res, 2 * r0 - r1 - r2 + c2 - a[r0] + 1);
	return res;
}
int main()
{
	read(n);
	for(int i = 1; i <= n; ++i)
		read(a[i]);
	read(Q);
	for(int i = 1; i <= Q; ++i)
	{
		int r1, c1, r2, c2;
		read(r1), read(c1), read(r2), read(c2);
		if(r1 <= r2) aks1[++cnt1] = (Akashi){r1, c1, r2, c2, i};
		else aks2[++cnt2] = (Akashi){n - r1 + 1, c1, n - r2 + 1, c2, i};
	}
	build();
	for(int i = 1; i <= cnt1; ++i)
	{
		Akashi now = aks1[i];
		int res = solve(now.r1, now.c1, now.r2, now.c2);
		ans[now.id] = res;
	}
	reverse(a + 1, a + n + 1);
	build();
	for(int i = 1; i <= cnt2; ++i)
	{
		Akashi now = aks2[i];
		int res = solve(now.r1, now.c1, now.r2, now.c2);
		ans[now.id] = res;
	}
	for(int i = 1; i <= Q; ++i)
		printf("%d\n", ans[i]);
	return 0;
}