E题题解
问题:已知第i个位置的数是vi,询问向右移动多少次可以到达vi。
vi >= i时
我们希望查询移动次数,即等于总距离(vi-i) - 跳过点的个数。对于i到vi的路径上,假如某个点可以跳过,那么这个点一定初始位置j>i,否则该点一直在i点左侧,无法被i点跳过;同时j点的目标点vj<vi,那么才能在路径上被跳过;最后该点的vj也需要>=j,否则该点无法在i跳到vi之前,跳到vj,对于vi做出贡献。那么就是查询同时满足以下条件的点:
1.j位于右侧区间(i,vi);
2.j的终点(vj)在区间(i,vi)
3.vj>=j
然后可以发现与操作相关的全是vk>=k的点,那么我们就只需要维护vk>=k的终点vk,最后查询(i,vi)区间有多少终点已经被得到就可以。
处理办法:我们从右向左(从n到1)利用树状数组进行区间查询即可。
vi < i时:
我们可以将逆序转换成顺序。处理方法:询问vi时,将vi+n,就变成了vi>=i问题,需要查询区间(i,vi+n),那么我们也需要对[n+1,2*n]区间进行维护,维护影响跳过次数的点。首先倍增区间,使得v[i+n] = v[i],i属于[1,n]。进行维护时,考虑影响跳过次数的点,可以发现点是满足该点目标节点在该点右侧,即某一点,i属于[n+1,2*n],v[i]+n >= i。
那么最后我们只需要从2*n 到n+1进行维护,再从n向1 遍历,一边查询答案,一边修改树状数组即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define _for(i,a,b) for(int (i)=(a); (i)<(b); ++(i))
#define _rep(i,a,b) for(int (i)=(a); (i)<=(b); ++(i))
#define lch(s) ('#'+s)
#define pii pair<int,int>
#define pll pair<ll,ll>
#define vint vector<int>
#define vll vector<ll>
#define endl '\n'
using ll = long long;
const ll maxn = 1e7+5;
const ll maxe = 1e7+5;
const ll N = 1e7+3;
const ll inf = 0x3f3f3f3f3f3f;
const ll mod = 1e9+7;
const ll inv = 5e8+4;
ll qpow(ll a,ll n)
{
ll res = 1;
while(n){
if(n&1) res = res*a%mod;
a=a*a%mod;
n>>=1;
}
return res;
}
ll v[maxn];
ll ans[maxn];
ll n,m,k;
#define lowbit(x) (x&(-x))
int tree[maxn];
void add(int x,int k)
{
for(x; x<=2*n; x+=lowbit(x)) tree[x]+=k;
}
int ask(int x)
{
int res = 0;
for(x; x>0; x-=lowbit(x)) res += tree[x];
return res;
}
void solve()
{
string s;
cin>>n;
_rep(i,1,2*n) tree[i] = 0;
_rep(i,1,n) cin>>v[i];
_rep(i,1,n) v[i+n] = v[i];
// 维护区间n+1到2*n
for(int i=2*n; i>=n+1; --i) if(v[i]+n >= i) add(v[i]+n,1);
// 遍历得到答案
for(int i=n; i>=1; --i)
{
if(v[i] >= i)
{
int tmp = ask(v[i]) - ask(i); // 跳过点数
ans[v[i]] = v[i] - i - tmp;
add(v[i] , 1);
}
if(v[i] < i)
{
int tmp = ask(v[i]+n) - ask(i); // 跳过点数
ans[v[i]] = v[i]+n-i - tmp;
add(v[i]+n,1);
}
}
_rep(i,1,n) cout<<ans[i]<<' ';
cout<<endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int T = 1;
cin>>T;
while(T--) solve();
return 0;
}