CodeTON Round 7 (Div. 1 + Div. 2, Rated, Prizes!) E题

E题题解

Problem - E - Codeforces

问题:已知第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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值