P8150
Solution
感觉难点在于如何想到三分。
很明显,如果我们确定了最小值的位置(显然,必须确定最小值的位置,然后找到其具体值),就可以往左右两端拓展,用 get 操作即可确定整个数列。
一个自然的想法是用二分,每次看最小值在左区间内还是在右区间内。但是我们很容易察觉到,我们找不到判断最小值在哪个区间内的方法。
于是考虑三分。我们取区间
然后又有一个我感觉想不到的操作。考虑
很明显,他们的
由于所有数互不相同,我们取三个区间的最小值分别是
所以考虑最小值在三个区间内的所有情况:
- 在最左边的区间中,必有前者不大于后者。
- 在中间的区间中,必有前者不等于后者。
- 在最右边的区间中,必有前者不小于后者。
然后你发现有一个很头疼的问题:如果我们找到的两个值并不相同,那么我们很容易舍掉最左边或者最右边的一个区间。
但是如果两个值相同,我们如何舍去最中间那个区间呢?
答曰:直接把他们删掉。因为最小值一定不在这里面,所以我们可以忽略不计,把左右区间拼起来即可。
实现的时候考虑搞一个双向链表,然后暴力找三等分点。
#include<bits/stdc++.h>
#define ll long long
#define ffor(i,a,b) for(int i=(a);i<=(b);i++)
#define roff(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
uint64_t query(int l, int r);
uint32_t get(int x);
const int MAXN=1e6+10;
uint32_t nxt[MAXN],pre[MAXN],hd,tl,len;
void del(uint32_t pos) {
len--;
uint32_t u=pre[pos],v=nxt[pos];
nxt[u]=v,pre[v]=u;
return ;
}
uint32_t get_kth(uint32_t k) {
uint32_t ans=0;
ffor(i,1,k) ans=nxt[ans];
return ans;
}
void dele(uint32_t l,uint32_t r) {
vector<uint32_t> aim;
uint32_t ans=0;
while(ans<l) ans=nxt[ans];
while(ans<=r) aim.push_back(ans),ans=nxt[ans];
for(auto id:aim) del(id);
return ;
}
std::vector<uint32_t> recover(int n) {
vector<uint32_t> ans(n);
hd=0,tl=n+1,len=n;
ffor(i,1,n) nxt[i]=i+1,pre[i]=i-1;
nxt[0]=1,pre[n+1]=n;
while(len>2) {
uint32_t l=nxt[hd],r=pre[tl],p1=get_kth(len/3),p2=get_kth(len-len/3+1);
uint64_t val1=query(l,p2-1)-query(l,p1),val2=query(p1+1,r)-query(p2,r);
if(val1<val2) dele(p2,r);
else if(val1>val2) dele(l,p1);
else dele(nxt[p1],pre[p2]);
}
uint32_t mnpos;
if(len==1) mnpos=nxt[hd],ans[mnpos-1]=get(mnpos);
else {
uint32_t pos1=nxt[hd],pos2=pre[tl],val1=get(pos1),val2=get(pos2);
if(val1<val2) mnpos=pos1,ans[mnpos-1]=val1;
else mnpos=pos2,ans[mnpos-1]=val2;
}
uint64_t lst=ans[mnpos-1];
ffor(i,mnpos+1,n) {
uint64_t val=query(mnpos,i)+ans[mnpos-1];
ans[i-1]=val-lst,lst=val;
}
lst=ans[mnpos-1];
roff(i,mnpos-1,1) {
uint64_t val=query(i,mnpos)+ans[mnpos-1];
ans[i-1]=val-lst,lst=val;
}
return ans;
}
不得不说,交互题属实有点。