[题解]高级监狱Ⅱ
\(\mathcal{Back\;To\;The\;Menu}\).
2022-02-08 高级监狱 Ⅱ
数列 / Array
莫名其妙发现了正解......考虑不降怎么做?事实上可以直接写转移方程:
如果我们将 \(a_i\) 看作 \(x\),那么我们想求
可以用李超树维护直线 \(y=-2a_j+a_j^2+f(j)\),最后枚举最后一个划分点在哪里,取 \(f(i)\) 最大值即可,复杂度 \(\mathcal O(n\log n)\).
接下来说正解,实际上直接把不降的代码拿去跑就可以过这道题......也就是说,我们直接将每个点视作它所在那一段的最大值直接转移即可,为什么这样是对的呢?假设最优情况是从 \(j\) 转移到 \(i\),不妨令 \(a_j<a_i\)(如果 \(a_j>a_i\),讨论是一样的),我们讨论几种情况:
- 存在 \(j<k<i\),且 \(a_j<a_i<a_k\),那么从 \(j\) 转移到 \(i\) 不如 \(j\to k\to i\) 优,与假设矛盾;
- 存在 \(j<k<i\),且 \(a_k<a_j<a_i\),那么从 \(j\) 转移到 \(i\) 不如 \(j\to k\to i\) 优,与假设矛盾;
- 在前两者都不存在的情况下,只有可能是 \(\forall k\in (j,i),a_j<a_k<a_i\),我们显然可以将划分点设在 \(j\),这样 \(j\) 就是前面那一段最大的了;
于是直接做就好了,复杂度 \(\mathcal O(n\log n)\).
/** @author Arextre */
#include <bits/stdc++.h>
using namespace std;
#define USING_FREAD
// #define NDEBUG
// #define NCHECK
#include <cassert>
namespace Elaina {
/** その可憐な少女は魔女であり、旅人でした。 ―― そう、私です! */
#define rep(i, l, r) for(int i = (l), i##_end_ = (r); i <= i##_end_; ++i)
#define drep(i, l, r) for(int i = (l), i##_end_ = (r); i >= i##_end_; --i)
#define fi first
#define se second
#define mp(a, b) make_pair(a, b)
#define Endl putchar('\n')
#define whole(v) ((v).begin()), ((v).end())
#define bitcnt(s) (__builtin_popcount(s))
/** @warning no forced type conversion */
#define rqr(x) ((x) * (x))
#define y0 FUCK_UP
#define y1 MOTHER_FUCKER
#ifdef NCHECK
# define iputs(Content) ((void)0)
# define iprintf(Content, argvs...) ((void)0)
#else
# define iputs(Content) fprintf(stderr, Content)
# define iprintf(Content, argvs...) fprintf(stderr, Content, argvs)
#endif
typedef long long ll;
typedef unsigned long long ull;
typedef std::pair<int, int> pii;
template<class T> inline T fab(T x) { return x < 0 ? -x : x; }
template<class T> inline void chkmin(T& x, const T rhs) { x = std::min(x, rhs); }
template<class T> inline void chkmax(T& x, const T rhs) { x = std::max(x, rhs); }
#ifdef USING_FREAD
inline char qkgetc() {
# define BUFFERSIZE 1 << 20
static char BUF[BUFFERSIZE], *p1 = BUF, *p2 = BUF;
return p1 == p2 && (p2 = (p1 = BUF) + fread(BUF, 1, BUFFERSIZE, stdin), p1 == p2) ? EOF : *p1++;
# undef BUFFERSIZE
}
# define CHARRECEI qkgetc()
#else
# define CHARRECEI getchar()
#endif
template<class T> inline T readret(T x) {
x = 0; int f = 0; char c;
while (!isdigit(c = CHARRECEI)) if(c == '-') f = 1;
for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
return f ? -x : x;
}
template<class T> inline void readin(T& x) {
x = 0; int f = 0; char c;
while (!isdigit(c = CHARRECEI)) if (c == '-') f = 1;
for (x = (c ^ 48); isdigit(c = CHARRECEI); x = (x << 1) + (x << 3) + (c ^ 48));
if (f) x = -x;
}
template<class T, class... Args> inline void readin(T& x, Args&... args) {
readin(x), readin(args...);
}
template<class T> inline void writln(T x, char c = '\n') {
if (x < 0) putchar('-'), x = -x;
static int __stk[55], __bit = 0;
do __stk[++__bit] = x % 10, x /= 10; while (x);
while (__bit) putchar(__stk[__bit--] ^ 48);
putchar(c);
}
} // namespace Elaina
using namespace Elaina;
const int maxn = 1e6;
const ll inf = 1ll << 60;
int n, c;
ll a[maxn + 5];
inline void input() {
readin(n, c);
rep (i, 1, n) readin(a[i]);
}
/** @warning the upper limit should be the maximum of @p {ai} */
namespace saya {
struct node {
ll k, b;
inline node() { k = 0, b = -inf; }
inline node(ll K, ll B): k(K), b(B) { /** nothing left */ }
inline ll operator ()(int x) { return k * x + b; }
} tre[maxn << 2 | 2];
#define ls (i << 1)
#define rs (i << 1 | 1)
#define mid (l + r >> 1)
#define _lhs ls, l, mid
#define _rhs rs, mid + 1, r
void insert(node f, int i = 1, int l = 1, int r = maxn) {
// totally superior
if (tre[i](l) <= f(l) && tre[i](r) <= f(r)) { tre[i] = f; return ; }
if (tre[i](mid) < f(mid)) swap(tre[i], f);
if (tre[i](l) < f(l)) insert(f, _lhs);
else if (tre[i](r) < f(r)) insert(f, _rhs);
}
ll query(int x, int i = 1, int l = 1, int r = maxn) {
ll ret = tre[i](x);
if (l == r) return ret;
if (x <= mid) return max(ret, query(x, _lhs));
else return max(ret, query(x, _rhs));
}
#undef ls
#undef rs
#undef mid
#undef _lhs
#undef _rhs
} // namespace saya
ll dp[maxn + 5];
signed main() {
freopen("array.in", "r", stdin);
freopen("array.out", "w", stdout);
input();
rep (i, 1, n) {
dp[i] = saya::query(a[i]) + rqr(a[i]) + c;
if (dp[i] < 0) dp[i] = 0;
saya::insert(saya::node(-(a[i] << 1), dp[i] + rqr(a[i])));
}
ll ans = 0;
rep (i, 1, n) chkmax(ans, dp[i]);
writln(ans);
return 0;
}
差量 / Difference
着实妙,只能说在交互、二分方面的积累太少了。然而也做过很多交互题,还是应当对交互有一个感觉的。一般的交互都是几个关键:有没有关键的地方?有什么是一个元素自己的特点?
就该题来说,独特点在于 每个元素互不相同,然而目前我们还不知道关键的地方,不妨先想一想怎么骗一点分。
就 \(n\le m_1\),是平凡的;当 \(n\le m_1+2m_2\) 时,我们也会做,先尽可能用 (1) 问出足够多的数,假设我们问出了 \(a_0,\cdots,a_{m_1-1}\),考虑如何使用两次 (2) 问出位置 \(p\) 的数 —— 很简单,用 (2) 询问 \(d_0=\text{qry}_2(a_0,a_p)\),再用 (2) 询问 \(d_1=(a_1,a_p)\),那么,在 \(a_0\pm d_0,a_1\pm d_1\) 这四个数中,两个相同的元素就是 \(a_p\).
当 \(n\le m_1+m_2\) 呢?此时要求使用一次 (2) 就可以确定元素,我们可以尝试用 (2) 询问 \(\text{qry}_2(a_0,a_1,a_p)\),此时我们固然可以得到 \(x=|a_0-a_p|,y=|a_1-a_p|\),但是现在有两个问题 —— 其一是我们不知道哪个是 \(x\),哪个是 \(y\),其二是,即使我们确定了 \(x,y\),但是不知道 \(a_p\) 和 \(a_0,a_1\) 之间的大小关系,此时 \(a_p\) 存在多解,除非一种情况出现:\(|a_1-a_0|=x+y\),这时 \(a_p\) 只有一个解。
不过不是所有元素都满足 \(\exists i,j\;\text{s.t.}\;a_i<x<a_j\),有两个特别的元素 —— 最大值和最小值。
此时我们通过骗分遇到的困难发现了 关键点 —— 最大值和最小值有点特别?将目光引到它俩上来之后,我们发现 特点 也可以在它俩上体现 —— 将所有元素与它俩中任意一个作差,每个元素的差一定是唯一的。这再次给予我们提示,关键点 可以体现 特点,那么突破口应当在关键点上。
既然如此,得想办法把关键点找出来啊?怎么找?这就得自己想一想了:全局询问一次,获得极差,然后对前缀做二分询问,如果该前缀的最大差值等于极差,则说明该前缀包含最大最小值。这样二分之后可以找到一个 端点,这个 端点 对应的数就是最大、最小值中的一个,至于是哪个,我们不需要立刻知道,因为知道它是最大或最小已经可以让我们利用 特点 了。
接下来就是关键了 —— 我们怎么得到每个元素分别与 端点 的差?考虑每次询问 \(S_i\) 集合的元素,它包含所有二进制下第 \(i\) 位为 \(1\) 的下标,再询问 \(S_i\cup\set{\textsf{端点}}\) 的差,就可以得到 \(S_i\) 中每个元素与 端点 的差,但是我们不知道差的下标啊?注意到差都是不一样的,所以我们将每个差出现的那些集合对应的二进制位拼起来,就可以还原出差的下标。后面的过程就非常朴素了。
注意将下标二进制分组也是一种很好用的 \(\log\) 式询问法。最后我们会用到 \(\mathcal O(3\log n)\) 次 (2) 询问,绰绰有余了。
#include "difference.h"
#include <bits/stdc++.h>
namespace Work_space {
const int maxn = 250;
vector<int> S, ret, ans, recei;
map<int, int> buc;
// composition: <delta, bit>
map<int, vector<int>> pos;
int delta[maxn + 5];
inline void find(int n, int M1, int M2) {
for (int i = 0; i < n; ++i) S.push_back(i);
recei = qry2(S);
sort(recei.begin(), recei.end());
int polar = recei.back();
int p = -1;
for (int l = 1, r = n - 1, mid; (mid = l + r >> 1, true) && l <= r; ) {
S.clear();
for (int i = 0; i <= mid; ++i) S.push_back(i);
recei = qry2(S);
sort(recei.begin(), recei.end());
if (recei.back() == polar) p = mid, r = mid - 1;
else l = mid + 1;
}
for (int b = 0; 1 << b <= n; ++b) {
S.clear(), buc.clear();
// add the index by 1
for (int i = 0; i < n; ++i)
if (i != p && (i + 1 >> b & 1)) S.push_back(i);
recei = qry2(S);
for (const int& v: recei) ++buc[v];
S.push_back(p); recei = qry2(S);
for (const int& v: recei) {
if (buc[v] > 0) --buc[v];
else pos[v].push_back(b);
}
}
// printf("p == %d\n", p);
assert(pos.size() == n - 1);
int mxdel = p;
for (const auto& [x, vec]: pos) {
int coord = 0;
for (const int& b: vec) coord += 1 << b;
--coord; // pay attention!!!
delta[coord] = x;
if (x > delta[mxdel]) mxdel = coord;
}
ans.resize(n);
ans[p] = qry1(p);
ans[mxdel] = qry1(mxdel);
int t = 1;
if (ans[p] > ans[mxdel]) t = -1;
for (int i = 0; i < n; ++i) if (i ^ p)
ans[i] = ans[p] + t * delta[i];
answer(ans);
return ;
}
} // namespace Work_space
void find(int n, int M1, int M2) { Work_space::find(n, M1, M2); }
异或 / Randomxor
留坑待补......