CF765F Souvenirs 题解
题目链接:CF 或者 洛谷
想了很久,然后想起做过的一道题:秃子酋长,一开始以为差不多,结果写着写着就发现不对劲了。最后写出了个神仙回滚莫队解法,感觉很妙,记录下。
进入神仙分析时刻
首先,我们来考虑一个事实,加上一个数以后,如果能找到它的前后驱,那么可以立马更新最优解,这个也即是瓶颈点。因为最优的绝对值最小值,一定是排序以后相邻位置的所有数对中选取才是最优的。而回滚莫队提供了一个很好的东西:如果增加和删除是互逆的过程,那么我就可以回滚?so?我们直接只加不减回滚莫队就完了吗?
当然没那么简单,考虑一个事实,你在链表或者一个序列上加上一个数,如何能
比较激动的开始码,问题来了,删除左端点后贡献该咋算,然后我想了下
检查,发现右端点可能和要删除的左端点有贡献被计算了,嗯,加个判断,写个变量
好了,写了半天,又 wa 了,如果同一个块,写一发暴力:
在途中,有过那么一瞬间冲动,在最后找到问题时,想要再无脑搞个链表单独维护右半边,想了大半天发现根本做不到一个较优复杂度,没办法无脑。一点也不好 “回滚”。当然肯定有用树类 ds 的更优解,但莫队的魅力就是这样的,看似暴力,其实处处充满着思考,令人喜爱。无论是常用的值域分块平衡修改与查询的复杂度,亦或者二次莫队离线的巧妙计算,都是充满着各种各样的探索与思考。最后拍了几波大数据和暴力对拍,过的时候还是很激动的。至此记录下思考心得。
参照代码
#include <bits/stdc++.h> // #pragma GCC optimize(2) // #pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math") // #pragma GCC target("sse,sse2,sse3,ssse3,sse4.1,sse4.2,avx,avx2,popcnt,tune=native") // #define isPbdsFile #ifdef isPbdsFile #include <bits/extc++.h> #else #include <ext/pb_ds/priority_queue.hpp> #include <ext/pb_ds/hash_policy.hpp> #include <ext/pb_ds/tree_policy.hpp> #include <ext/pb_ds/trie_policy.hpp> #include <ext/pb_ds/tag_and_trait.hpp> #include <ext/pb_ds/hash_policy.hpp> #include <ext/pb_ds/list_update_policy.hpp> #include <ext/pb_ds/assoc_container.hpp> #include <ext/pb_ds/exception.hpp> #include <ext/rope> #endif using namespace std; using namespace __gnu_cxx; using namespace __gnu_pbds; typedef long long ll; typedef long double ld; typedef pair<int, int> pii; typedef pair<ll, ll> pll; typedef tuple<int, int, int> tii; typedef tuple<ll, ll, ll> tll; typedef unsigned int ui; typedef unsigned long long ull; typedef __int128 i128; #define hash1 unordered_map #define hash2 gp_hash_table #define hash3 cc_hash_table #define stdHeap std::priority_queue #define pbdsHeap __gnu_pbds::priority_queue #define sortArr(a, n) sort(a+1,a+n+1) #define all(v) v.begin(),v.end() #define yes cout<<"YES" #define no cout<<"NO" #define Spider ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr); #define MyFile freopen("..\\input.txt", "r", stdin),freopen("..\\output.txt", "w", stdout); #define forn(i, a, b) for(int i = a; i <= b; i++) #define forv(i, a, b) for(int i=a;i>=b;i--) #define ls(x) (x<<1) #define rs(x) (x<<1|1) #define endl '\n' //用于Miller-Rabin [[maybe_unused]] static int Prime_Number[13] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37}; template <typename T> int disc(T* a, int n) { return unique(a + 1, a + n + 1) - (a + 1); } template <typename T> T lowBit(T x) { return x & -x; } template <typename T> T Rand(T l, T r) { static mt19937 Rand(time(nullptr)); uniform_int_distribution<T> dis(l, r); return dis(Rand); } template <typename T1, typename T2> T1 modt(T1 a, T2 b) { return (a % b + b) % b; } template <typename T1, typename T2, typename T3> T1 qPow(T1 a, T2 b, T3 c) { a %= c; T1 ans = 1; for (; b; b >>= 1, (a *= a) %= c)if (b & 1)(ans *= a) %= c; return modt(ans, c); } template <typename T> void read(T& x) { x = 0; T sign = 1; char ch = getchar(); while (!isdigit(ch)) { if (ch == '-')sign = -1; ch = getchar(); } while (isdigit(ch)) { x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar(); } x *= sign; } template <typename T, typename... U> void read(T& x, U&... y) { read(x); read(y...); } template <typename T> void write(T x) { if (typeid(x) == typeid(char))return; if (x < 0)x = -x, putchar('-'); if (x > 9)write(x / 10); putchar(x % 10 ^ 48); } template <typename C, typename T, typename... U> void write(C c, T x, U... y) { write(x), putchar(c); write(c, y...); } template <typename T11, typename T22, typename T33> struct T3 { T11 one; T22 tow; T33 three; bool operator<(const T3 other) const { if (one == other.one) { if (tow == other.tow)return three < other.three; return tow < other.tow; } return one < other.one; } T3() { one = tow = three = 0; } T3(T11 one, T22 tow, T33 three) : one(one), tow(tow), three(three) { } }; template <typename T1, typename T2> void uMax(T1& x, T2 y) { if (x < y)x = y; } template <typename T1, typename T2> void uMin(T1& x, T2 y) { if (x > y)x = y; } constexpr int N = 3e5 + 10; int pre[N], nxt[N]; //链表前后 int pos[N]; //序列分块 struct Mo { int l, r, id; bool operator<(const Mo& other) const { return pos[l] ^ pos[other.l] ? l < other.l : r < other.r; } } node[N]; constexpr int INF = 1e9 + 7; int curr = INF; pii a[N]; int val[N], idx[N], mp[N]; //排序以后的值、每个点对应排序后的编号、每个排序后的编号对应的原下标 int split; //分割点 //删除 inline void del(const int id) { const int prePos = pre[id]; const int nxtPos = nxt[id]; nxt[prePos] = nxtPos; pre[nxtPos] = prePos; if (mp[nxtPos] > split and mp[prePos] > split) uMin(curr, val[nxtPos] - val[prePos]); } //增加,只考虑分割点右侧不需要回滚的贡献 inline void add(const int id) { const int prePos = pre[id]; const int nxtPos = nxt[id]; pre[nxtPos] = id; nxt[prePos] = id; if (mp[id] <= split)return; if (mp[prePos] > split)uMin(curr, val[id] - val[prePos]); if (mp[nxtPos] > split)uMin(curr, val[nxtPos] - val[id]); } int n, q; int ans[N]; int siz; int tmpVal[N], tmpCnt; int old[N]; //计数 inline int cale(const int id) { const int prePos = pre[id]; const int nxtPos = nxt[id]; int res = INF; if (nxtPos)uMin(res, val[nxtPos] - val[id]); if (prePos)uMin(res, val[id] - val[prePos]); return res; } inline void solve() { cin >> n; siz = sqrt(n); forn(i, 1, n)cin >> a[i].first, old[i] = a[i].first, a[i].second = i, pos[i] = (i - 1) / siz + 1; sortArr(a, n); forn(i, 1, n)idx[a[i].second] = i, mp[i] = a[i].second, val[i] = a[i].first; cin >> q; forn(i, 1, q) { auto& [l,r,id] = node[i]; cin >> l >> r, id = i; } sortArr(node, q); int l = 1, r = n, last = 0; forn(i, 1, n)pre[i] = i - 1, nxt[i] = i + 1; nxt[n] = 0; forn(i, 1, q) { auto [L,R,id] = node[i]; if (pos[L] == pos[R]) { tmpCnt = 0; forn(i, L, R)tmpVal[++tmpCnt] = old[i]; sortArr(tmpVal, tmpCnt); int res = INF; forn(i, 1, tmpCnt-1)uMin(res, tmpVal[i + 1] - tmpVal[i]); ans[id] = res; continue; } if (pos[L] != last) { const int start = (pos[L] - 1) * siz + 1; const int end = min(pos[L] * siz, n); //回滚莫维护链表形状的精髓之章,建议反复体会回滚莫的互逆核心 while (r < n)add(idx[++r]); while (l < start)del(idx[l++]); while (r > end)del(idx[r--]); curr = INF; last = pos[L]; split = end; } while (r < R)add(idx[++r]); //需要回滚 int tmpL = l, preCurr = curr; while (tmpL < L)del(idx[tmpL++]); int res = curr; forn(i, L, split)uMin(res, cale(idx[i])); //暴力再统计左半部分对全局的情况 ans[id] = res; while (tmpL > l)add(idx[--tmpL]); //回滚 curr = preCurr; } forn(i, 1, q)cout << ans[i] << endl; } signed int main() { // MyFile Spider //------------------------------------------------------ // clock_t start = clock(); int test = 1; // read(test); // cin >> test; forn(i, 1, test)solve(); // while (cin >> n, n)solve(); // while (cin >> test)solve(); // clock_t end = clock(); // cerr << "time = " << double(end - start) / CLOCKS_PER_SEC << "s" << endl; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统