CF1876G Clubstep
原题链接 CF1876G Clubstep。
DX 上课讲的,有趣啊。
考虑暴力咋做。首先肯定不会选择一个 \(>r\) 的 \(p\) 来做操作,因为不如在 \(r\) 处做操作。那么一开始我们肯定要在 \(r\) 处做 \(\max(0,\lceil\dfrac{x-a_r}{2}\rceil)\) 次操作,然后接着往前做。
但是这样每次序列的值会变,发现在点 \(r\) 进行 \(k\) 次操作后,不修改 \([1,r)\) 的 \(a\) 值,而将 \(x\gets x-k\) 是等价的,于是便可以递归到 \([l,r)\) 的子问题。
考虑对于所有询问同时处理,从 \(n\) 扫到 \(1\),在扫到一个询问右端点时加入,左端点时删除。
维护一个优先队列,每次取出所有目前 \(x>a_i\) 的询问进行处理,然后再放回去。发现是否进行操作只与 \(x\) 值有关。
发现优先队列中相邻两个数的差值是 \(O(V)\) 的,当这两个询问同时进行操作后,它们的差值会折半,即 \(O(\log V)\) 次后这两个询问的 \(x\) 值将变得一样,可以合并起来。
也就是说设某次取出来 \(a\) 个数,其中就会有 \(a-1\) 个数与其前驱的差值折半,最多折半 \((O\log V)\) 次,所以最多总共进行 \(O(n+n\log V)\) 次操作。
问题在于维护答案,用带权并查集维护,可以将一个点的答案当做从其到其祖先的权值和,把 \(u\) 合并到 \(v\) 时将 \(f_u\gets f_u-f_v\)。再在路径压缩时记录从点 \(u\) 到其祖先(不含祖先)的权值和即可。或者直接启发式合并暴力跳也行,反正不是复杂度瓶颈。
总时间复杂度是 \(O(n\log nV)\) 的。
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#define int long long
#define ll long long
using namespace std;
typedef pair<int,int> P;
const int MAXN=3e5+10;
int n,m,a[MAXN],fa[MAXN],x[MAXN],val[MAXN];
ll ans[MAXN],sum[MAXN];
vector <int> L[MAXN],R[MAXN];
priority_queue <P> q;vector <P> cur;
inline int find(int x)
{
if(fa[x]==x) return x;
if(fa[fa[x]]==fa[x]) return fa[x];
int fax=find(fa[x]);
sum[x]+=sum[fa[x]];return fa[x]=fax;
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.in","r",stdin);
freopen("out.out","w",stdout);
#endif
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n;for(int i=1;i<=n;++i) cin>>a[i];
cin>>m;for(int i=1,l,r;i<=m;++i)
cin>>l>>r>>x[i],fa[i]=i,
L[l].push_back(i),R[r].push_back(i);
for(int i=n;i;--i)
{
for(int p:R[i]) q.push({x[p],p});
while(!q.empty()&&q.top().first>=a[i])
cur.push_back(q.top()),q.pop();
for(int j=cur.size()-1,pre=-1,num;~j;--j)
{
int x=cur[j].first,pos=cur[j].second;
int k=(x-a[i]+1)/2;x-=k,sum[pos]+=k*i;
if(pre!=-1&&x==num)
fa[pos]=pre,sum[pos]-=sum[pre];
else q.push({x,pos}),pre=pos,num=x;
}
cur.clear();
for(int p:L[i])
{
find(p),ans[p]=sum[p];
if(fa[p]!=p) ans[p]+=sum[fa[p]];
}
}
for(int i=1;i<=m;++i) cout<<ans[i]<<'\n';
return 0;
}