P9376 题解
首先考虑怎么暴力。
考虑把每个数进行 \(B\) 进制分解,然后我们惊奇的发现这两个操作就是把最低位去掉和往最低位后面插入一个数。
然后我们顺藤摸瓜,把每个数的分解扔到 Trie 树上,我们发现我们要找到一个节点,使得所有单词节点到其的距离之和最短,答案就是这个最短距离。
这里直接考虑一个 Trie 树上 dp,记所有单词节点到节点 \(i\) 的最短距离为 \(dp_i\),然后直接去转移。
然后考虑找找性质。
记 \(sz_i\) 表示以节点 \(i\) 为根的子树内单词节点数量,我们发现节点 \(i\) 的转移如下 \(dp_i = dp_{fa_i} - sz_j + (sz_1 - sz_j)\)。
又因为 \(sz_i \leq sz_{fa_i}\),所以只有当节点 \(i\) 满足 \(sz_i \times 2 > sz_1\) 进入到以 \(i\) 为根的子树转移才最优。
而我们又发现对于一个节点满足条件的子节点至多只有 \(1\) 个。
也就是说如果把最优答案在树上的转移画出来,并称其为最优路径,那么首先这一定是一条从根出发的路径,而且以这条路径所代表的数为前缀的数一定超过总数的一半。
然后有两种优化方向。
第一种是利用可持久化 Trie 树预处理,然后直接在 Trie 上利用刚刚的性质暴力 \(\log_{B} V\) 查询,缺点是对于每个 \(B\) 都要建一棵树。
第二种是因为我们只在乎最优路径上的转移,所以我们随机抽取 \(\log n\) 个节点放到 Trie 树上,显然因为这个性质类似于绝对众数的性质,因此最优的转移路径不在 Trie 上出现的可能只有 \(\frac{1}{n}\)。那么多抽取几个节点就可以基本保证一定会出现。
那么转移路径找出来了,现在问题是转移中 \(sz_i\) 怎么求?
我们有发现 \(sz_i\) 等价于 \(B\) 进制下以从根节点到节点 \(i\) 所表示的数为前缀的数的数量,而这个可以枚举这个前缀后面有几位数转变成一个连续区间上查询权值为 \([L,R]\) 的数的数量,这个可以直接主席树来搞。这么做缺点是复杂度是 \(n \log_{B}^3 V\)。
考虑到 \(\log_{2} n\) 一般远大于 \(\log_{3} n\)。
所以结合两种算法,用第一种算法解决 \(B = 2\) 的询问,用第二种算法解决其他询问。
那么就做完了。
代码极丑,慎入。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int top = 100000001;
const int maxn = 1e6+114;
struct Node{
int sz;//当前这个节点子树内有多少个单词节点
vector <pair<int,int> > edge;//边
}Trie[maxn];
struct node{
int sz;
int ls,rs;
}_01trie[maxn*30];
struct NODE{
int val;
int ls,rs;
}SGTtree[maxn*30];
int SGTroot[maxn];
int SGTtot=1;
int SGTask(int ql,int qr,int lt,int rt,int L,int R){
if(rt<ql||lt>qr){
return 0;
}
if(ql<=lt&&rt<=qr){
return SGTtree[R].val-SGTtree[L].val;
}
int mid=(lt+rt)/2;
int res=0;
res+=SGTask(ql,qr,lt,mid,SGTtree[L].ls,SGTtree[R].ls);
res+=SGTask(ql,qr,mid+1,rt,SGTtree[L].rs,SGTtree[R].rs);
return res;
}
int SGTquery(int l,int r,int cl,int cr){
return SGTask(cl,cr,1,top,SGTroot[l-1],SGTroot[r]);
}
void SGTupdate(int cur,int lst,int lt,int rt,int pos,int v){
SGTtree[cur].val=SGTtree[lst].val+v;
if(lt==rt){
return ;
}
int mid=(lt+rt)/2;
if(pos<=mid){
SGTtree[cur].rs=SGTtree[lst].rs;
SGTtree[cur].ls=++SGTtot;
SGTupdate(SGTtree[cur].ls,SGTtree[lst].ls,lt,mid,pos,v);
}
else{
SGTtree[cur].ls=SGTtree[lst].ls;
SGTtree[cur].rs=++SGTtot;
SGTupdate(SGTtree[cur].rs,SGTtree[lst].rs,mid+1,rt,pos,v);
}
}
int qpow(int a,int b){
if(b==0) return 1;
if(b==1) return min(a,top);
int res=min(qpow(a,b/2),top);
res=min(top,res*res);
if(b%2==1) res=min(res*a,top);
return min(top,res);
}
void SGTadd(int pos,int val){
SGTroot[pos]=++SGTtot;
SGTupdate(SGTroot[pos],SGTroot[pos-1],1,top,val,1);
}
int SUMQUERY(int B,int L,int R){//查询 B 进制下区间 [L,R] 所有数长度之和
int res=0;
for(int k=1;k<=30;k++){
int l=min(top,qpow(B,k-1)),r=min(top,qpow(B,k)-1);
res+=SGTquery(L,R,l,r)*k;
if(r==top) break;
}
return res;
}
int PREQUERY(int x,int B,int L,int R){//查询 B 进制下区间 [L,R] 内多少个数前缀为 x
int res=0;
for(int k=0;k<=30;k++){
int l=min(top,x*qpow(B,k)),r=min(top,(x+1)*qpow(B,k)-1);
res+=SGTquery(L,R,l,r);
if(r==top) break;
}
return res;
}
int tot=1,anser;
int _01tot=1;
int sum;
int n,m;
int flag;
stack<int> s;
int root[maxn],Sum[maxn];
void _01insert(int cur,int lst){
_01trie[cur].sz++;
s.pop();
if(s.size()==0) return ;
if(s.top()==0){
_01trie[cur].rs=_01trie[lst].rs;
_01trie[cur].ls=++_01tot;
_01trie[_01trie[cur].ls].sz+=_01trie[_01trie[lst].ls].sz;
_01insert(_01trie[cur].ls,_01trie[lst].ls);
}
else{
_01trie[cur].ls=_01trie[lst].ls;
_01trie[cur].rs=++_01tot;
_01trie[_01trie[cur].rs].sz+=_01trie[_01trie[lst].rs].sz;
_01insert(_01trie[cur].rs,_01trie[lst].rs);
}
}
void _01dfs(int l,int r,int ans,int L,int R){
if(l==0&&r==0) return ;
anser=min(anser,ans);
if(_01trie[_01trie[r].ls].sz-_01trie[_01trie[l].ls].sz>_01trie[_01trie[r].rs].sz-_01trie[_01trie[l].rs].sz){
_01dfs(_01trie[l].ls,_01trie[r].ls,ans-(_01trie[_01trie[r].ls].sz-_01trie[_01trie[l].ls].sz)+(_01trie[R].sz-_01trie[L].sz-(_01trie[_01trie[r].ls].sz-_01trie[_01trie[l].ls].sz)),L,R);
}
else{
_01dfs(_01trie[l].rs,_01trie[r].rs,ans-(_01trie[_01trie[r].rs].sz-_01trie[_01trie[l].rs].sz)+(_01trie[R].sz-_01trie[L].sz-(_01trie[_01trie[r].rs].sz-_01trie[_01trie[l].rs].sz)),L,R);
}
}
int _01query(int l,int r){
anser=INT_MAX;
_01dfs(root[l-1],root[r],Sum[r]-Sum[l-1],root[l-1],root[r]);
return anser;
}
void _01add(int x,int B,int pos){
while(x!=0){
s.push(x%B);
x/=B;
}
s.push(0);
Sum[pos]=s.size()-1+Sum[pos-1];
root[pos]=++_01tot;
_01trie[root[pos]].sz+=_01trie[root[pos-1]].sz;
_01insert(root[pos],root[pos-1]);
}
void insert(int cur,int val){
Trie[cur].sz+=val;
s.pop();
if(s.size()==0) return ;
for(pair<int,int> v:Trie[cur].edge){
if(v.second==s.top()){
insert(v.first,val);
return ;
}
}
Trie[cur].edge.push_back(make_pair(++tot,s.top()));
insert(tot,val);
}
void add(int x,int B){
while(x!=0){
s.push(x%B);
x/=B;
}
s.push(0);
sum+=s.size()-1;
insert(1,1);
}
void dfs(int cur,int ans,int PRE,int S,int B,int L,int R){
anser=min(anser,ans);
for(pair<int,int> v:Trie[cur].edge){
int nxt=PRE*B+v.second;
int g=PREQUERY(nxt,B,L,R);
if((S-g)-g>=0) continue;
dfs(v.first,ans-g+(S-g),nxt,S,B,L,R);
}
}
int query(int B,int L,int R){
anser=INT_MAX;
dfs(1,SUMQUERY(B,L,R),0,(R-L+1),B,L,R);
return anser;
}
void clear(){
for(int i=1;i<=tot;i++){
Trie[i].edge.clear();
Trie[i].sz=0;
}
sum=0;
tot=1;
}
int a[maxn];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
SGTadd(i,a[i]);
_01add(a[i],2,i);
}
while(m--){
int l,r,B;
cin>>l>>r>>B;
if(B==2){
cout<<_01query(l,r)<<"\n";
}
else{
clear();
for(int j=1;j<=22;j++){
int x=rand()%(r-l+1)+l;
add(a[x],B);
}
cout<<query(B,l,r)<<'\n';
}
}
}
/*
5 2
7 6 5 8 9
1 3 2
2 5 2
*/