CF2203F Binary Search with One Swap
CF2203F Binary Search with One Swap
首先,不难发现,对于调换 \(i,j\),只有 \(i,j\) 之间的数才会受到影响。因为对于任何一个数,只要它左边的数都比它小,右边的数都比它大,那么通过这样的二分一定可以找到它。于是我们便要计算对于 \(i,j\) 之间的数有多少是找不到的。
考虑把二分的过程建成一棵树。对于每个节点,它的左儿子是它左边区域的 \(mid\),它的右儿子是它右边区域的 \(mid\)。这样的话二分的过程就是树上的一条路径。对于一个节点 \(i\),如果这条路径不经过它,那么它是任意值对于我查找都是没有影响的。
于是,对于调换 \(i,j\),\(i,j\) 之间的在 \(i\) 或 \(j\) 子树中的节点是无法被找到的。
如果它们不存在祖先后代关系,假设 \(i<j\),那么就是 \(i\)的右子树的大小加上 \(j\) 的左子树的大小。
如果它们之间存在祖先后代关系,那么无法被找到的数的个数就是 \(|i-j|\)。因为全都在 \(i\) 或 \(j\) 的子树中。
那么如何计算答案呢?对于第一种类型,统计对于每一个 \(j\),有多少个 \(i<j\),使得 \(r_{i}+l_{j}+2=k\)。根据二分构建的数必然是比较均匀的,因此不同的子树大小个数只有 \(\log\) 个,直接枚举即可。对于第二类,我们直接枚举每一个点,然后不断向上跳祖先即可。但是,在统计第一类贡献时,我们是直接枚举的 \(r_{i}\),并没有考虑 \(i,j\) 是否不存在祖先后代关系,因此在向上跳祖先的过程中要把这些情况减去。
\(code\)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e6+5;
int n;
int fa[N],lsiz[N],rsiz[N],siz[N],cnt[N];
ll ans[N];
map<int,int> mp;
vector<int> cntr;
int build(int l,int r)
{
if (l>r) return 0;
if (l==r)
{
siz[l]=1;
mp[0]=1;
return l;
}
int mid=l+r>>1;
int lson=build(l,mid-1),rson=build(mid+1,r);
lsiz[mid]=siz[lson],rsiz[mid]=siz[rson];
fa[lson]=fa[rson]=mid;
siz[mid]=siz[lson]+siz[rson]+1;
mp[rsiz[mid]]=1;
return mid;
}
inline void solve()
{
cin>>n;
build(1,n);
for (auto [x,y]:mp) cntr.push_back(x);
for (int i=1;i<=n;++i)
{
for (int j:cntr) if (j+lsiz[i]+2<=n) ans[j+lsiz[i]+2]+=cnt[j];
++cnt[rsiz[i]];
int cur=fa[i];
while (cur)
{
if (cur>i)
{
if (rsiz[i]+lsiz[cur]+2<=n) --ans[rsiz[i]+lsiz[cur]+2];
}
else
{
if (lsiz[i]+rsiz[cur]+2<=n) --ans[lsiz[i]+rsiz[cur]+2];
}
++ans[abs(cur-i)];
cur=fa[cur];
}
}
for (int i=n;i>=0;--i) cout<<ans[i]<<" ";
cout<<endl;
}
int main()
{
ios::sync_with_stdio(false);
solve();
return 0;
}
浙公网安备 33010602011771号