前言
被学长逼着跳过来学莫队了 qwq......
一、普通莫队
直接在第一个题里介绍吧。
1.莫队 / 小 B 的询问
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n,m,k;
cin>>n>>m>>k;
vector<int>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<array<int,3>>query(m+1);
for(int i=1;i<=m;i++)
{
cin>>query[i][0]>>query[i][1];
query[i][2]=i;
}
int blen=sqrt(n);
vector<int>bi(n+1);
for(int i=1;i<=n;i++)
{
bi[i]=(i-1)/blen+1;
}
//classic
auto cmp1=[&](auto &x,auto &y)->bool
{
if(bi[x[0]]!=bi[y[0]])
{
return bi[x[0]]<bi[y[0]];
}
return x[1]<y[1];
};
//parity
auto cmp2=[&](auto &x,auto &y)->bool
{
if(bi[x[0]]!=bi[y[0]])
{
return bi[x[0]]<bi[y[0]];
}
return bi[x[0]]%2?x[1]<y[1]:x[1]>y[1];
};
sort(query.begin()+1,query.end(),cmp2);
ll sum=0;
vector<ll>cnts(k+1);
auto erase=[&](int x)->void
{
sum-=cnts[x]*cnts[x];
cnts[x]--;
sum+=cnts[x]*cnts[x];
};
auto add=[&](int x)->void
{
sum-=cnts[x]*cnts[x];
cnts[x]++;
sum+=cnts[x]*cnts[x];
};
vector<ll>ans(m+1);
for(int l=1,r=0,i=1;i<=m;i++)
{
auto [jobl,jobr,id]=query[i];
while(jobl<l)
{
add(a[--l]);
}
while(r<jobr)
{
add(a[++r]);
}
while(l<jobl)
{
erase(a[l++]);
}
while(jobr<r)
{
erase(a[r--]);
}
ans[id]=sum;
}
for(int i=1;i<=m;i++)
{
cout<<ans[i]<<endl;
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
因为每次要查范围内所有数词频的平方,那么如果不考虑多条查询,就可以直接通过一个 cnts 词频数组维护。之后遍历整个区间,每次先减去之前的贡献,更新词频,再加上当前的贡献即可。
此时可以发现,如果要让这个区间扩大或者缩小,是可以做到 完成信息的更新。那么对于就可以将其看作一个窗口,每次根据区间扩大或缩小即可,但若查询的区间长度是 n 和 1 交替出现,那么窗口就会频繁扩大缩小,复杂度就会降低为
,需要考虑优化。
考虑 将所有查询离线下来 ,之后的操作是重点。首先,**对整个数组进行分块,**之后对于离线下来的所有查询,存在两种处理策略。虽然两种策略的时间复杂度是一样的,但第二种策略的常数时间会更好一点。
第一种策略,考虑 先根据左边界所在块号从小到大排序,再根据右边界数值从小到大排序 。此时,依次对每条查询暴力滑窗,复杂度就可以做到 。之后,考虑证明这个复杂度,分为块内和块间两部分进行分析。
对于块内,因为右边界是从小到大排序的,所以最多就是整个数组都滑一遍,所以复杂度 。又因为一共
个块,所以右边界整体复杂度就是
的。对于左边界,由于其是乱序的,所以最差情况是左边界频繁左右滑动。但由于每个块的长度是
的,所以每条查询左边界的代价就是
的。那么对于每个块,左边界移动的规模就可以看作
。
对于两个块间切换时的代价,就是让上一个块的最后一个窗口变成当前块的第一个窗口的代价。又因为左右边界滑动的代价最多就是 的,且一共就
块,所以总代价就是
。
对于第二种策略,注意到在块与块切换的时候,每次窗口的右边界是有可能滑过一个很大的范围的。所以可以考虑对于右边界,奇数块从小到大排序,偶数块从大到小排序。虽然这样看似能很大程度上优化复杂度,但若所有查询都在奇数块内,这样写其实和上一种策略是一样的。
实现方面,考虑用闭区间表示窗口,初始左边界为 1 右边界为 0。然后每次都判断左右是否需要扩以及左右是否需要缩,写好 add 和 erase 函数即可。
2.小 Z 的袜子
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
template<typename T>
T gcd(T a,T b){
return b==0?a:gcd(b,a%b);
}
template<typename T>
T lcm(T a,T b){
return a*b/gcd(a,b);
}
void solve()
{
int n,m;
cin>>n>>m;
vector<int>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<array<int,3>>query(m+1);
for(int i=1;i<=m;i++)
{
cin>>query[i][0]>>query[i][1];
query[i][2]=i;
}
int blen=sqrt(n);
vector<int>bi(n+1);
for(int i=1;i<=n;i++)
{
bi[i]=(i-1)/blen+1;
}
//classic
auto cmp1=[&](auto &x,auto &y)->bool
{
if(bi[x[0]]!=bi[y[0]])
{
return bi[x[0]]<bi[y[0]];
}
return x[1]<y[1];
};
//parity
auto cmp2=[&](auto &x,auto &y)->bool
{
if(bi[x[0]]!=bi[y[0]])
{
return bi[x[0]]<bi[y[0]];
}
return bi[x[0]]%2?x[1]<y[1]:x[1]>y[1];
};
sort(query.begin()+1,query.end(),cmp1);
ll sum=0;
vector<ll>cnts(n+1);
auto erase=[&](int x)->void
{
sum-=cnts[x]*cnts[x];
cnts[x]--;
sum+=cnts[x]*cnts[x];
};
auto add=[&](int x)->void
{
sum-=cnts[x]*cnts[x];
cnts[x]++;
sum+=cnts[x]*cnts[x];
};
vector<array<ll,2>>ans(m+1);
for(int l=1,r=0,i=1;i<=m;i++)
{
auto [jobl,jobr,id]=query[i];
while(jobl<l)
{
add(a[--l]);
}
while(r<jobr)
{
add(a[++r]);
}
while(l<jobl)
{
erase(a[l++]);
}
while(jobr<r)
{
erase(a[r--]);
}
if(jobl==jobr)
{
ans[id][0]=0;
ans[id][1]=1;
}
else
{
ll k=jobr-jobl+1;
ll up=sum-k;
ll down=k*(k-1);
ll g=gcd(up,down);
ans[id][0]=up/g;
ans[id][1]=down/g;
}
}
for(int i=1;i<=m;i++)
{
cout<<ans[i][0]<<"/"<<ans[i][1]<<endl;
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
若区间长度为 k,a 这个数的词频为 a,那么选出 a 这个数的概率就是 。将所有数的概率累加起来,就有
,拆开就有
。此时可以发现第一个式子的分子就是上个题的答案,第二个式子的分子就是 k 本身,所以答案就是
,同除 gcd 化简即可。
3.大爷的字符串题
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n,q;
cin>>n>>q;
vector<ll>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<array<int,3>>query(q+1);
for(int i=1;i<=q;i++)
{
cin>>query[i][0]>>query[i][1];
query[i][2]=i;
}
vector<ll>sorted(n+1);
for(int i=1;i<=n;i++)
{
sorted[i]=a[i];
}
sort(sorted.begin()+1,sorted.end());
int m=1;
for(int i=2;i<=n;i++)
{
if(sorted[i]!=sorted[m])
{
sorted[++m]=sorted[i];
}
}
auto bs=[&](int v)->int
{
int l=1;
int r=m;
int mid;
int ans;
while(l<=r)
{
mid=l+r>>1;
if(sorted[mid]>=v)
{
ans=mid;
r=mid-1;
}
else
{
l=mid+1;
}
}
return ans;
};
for(int i=1;i<=n;i++)
{
a[i]=bs(a[i]);
}
int blen=sqrt(n);
vector<int>bi(n+1);
for(int i=1;i<=n;i++)
{
bi[i]=(i-1)/blen+1;
}
sort(query.begin()+1,query.end(),[&](auto &x,auto &y)
{
if(bi[x[0]]!=bi[y[0]])
{
return bi[x[0]]<bi[y[0]];
}
return x[1]<y[1];
});
int maxCnt=0;
vector<int>cnts(n+1);
vector<int>kinds(n+1);
auto add=[&](int v)->void
{
kinds[cnts[v]]--;
cnts[v]++;
kinds[cnts[v]]++;
maxCnt=max(maxCnt,cnts[v]);
};
auto erase=[&](int v)->void
{
if(cnts[v]==maxCnt&&kinds[cnts[v]]==1)
{
maxCnt--;
}
kinds[cnts[v]]--;
cnts[v]--;
kinds[cnts[v]]++;
};
vector<int>ans(q+1);
for(int l=1,r=0,i=1;i<=q;i++)
{
auto [jobl,jobr,id]=query[i];
while(jobl<l)
{
add(a[--l]);
}
while(r<jobr)
{
add(a[++r]);
}
while(l<jobl)
{
erase(a[l++]);
}
while(jobr<r)
{
erase(a[r--]);
}
ans[id]=-maxCnt;
}
for(int i=1;i<=q;i++)
{
cout<<ans[i]<<endl;
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
牛魔的,这题自己读了半天没读明白,直接看左神的形式化题意了......
这道题其实就是每次求区间内众数的次数的相反数,那么因为只考虑数字的词频,所以就可以先对其离散化。之后,由于需要维护窗口中众数的词频 maxcnt,肯定需要维护每个数的词频 cnts,每次对入窗口的数看其词频能否更新 maxcnt。但在出窗口时,若当前数不是 maxcnt,那么是不用管的。但如果当前数是 maxcnt,此时就需要判断是否还有别的数词频同样是 maxcnt,所以考虑再维护一个 kinds 表示词频为 i 的数有几种。入窗口减去之前的加上当前的,出窗口时判断此时词频为 maxcnt 的数的个数是否为 1 即可。
4.异或序列
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n,m,k;
cin>>n>>m>>k;
vector<int>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<array<int,3>>query(m+1);
for(int i=1;i<=m;i++)
{
cin>>query[i][0]>>query[i][1];
query[i][2]=i;
}
vector<int>pre(n+1);
for(int i=1;i<=n;i++)
{
pre[i]=pre[i-1]^a[i];
}
int blen=sqrt(n);
vector<int>bi(n+1);
for(int i=1;i<=n;i++)
{
bi[i]=(i-1)/blen+1;
}
sort(query.begin()+1,query.end(),[&](auto &x,auto &y)
{
if(bi[x[0]]!=bi[y[0]])
{
return bi[x[0]]<bi[y[0]];
}
return x[1]<y[1];
});
const int MAXP=1<<17;
vector<ll>cnts(MAXP);
ll sum=0;
auto add=[&](int v)->void
{
if(k)
{
sum-=cnts[v]*cnts[v^k];
}
else
{
sum-=(cnts[v]*(cnts[v]-1))/2;
}
cnts[v]++;
if(k)
{
sum+=cnts[v]*cnts[v^k];
}
else
{
sum+=(cnts[v]*(cnts[v]-1))/2;
}
};
auto erase=[&](int v)->void
{
if(k)
{
sum-=cnts[v]*cnts[v^k];
}
else
{
sum-=(cnts[v]*(cnts[v]-1))/2;
}
cnts[v]--;
if(k)
{
sum+=cnts[v]*cnts[v^k];
}
else
{
sum+=(cnts[v]*(cnts[v]-1))/2;
}
};
vector<ll>ans(m+1);
for(int l=1,r=0,i=1;i<=m;i++)
{
auto [jobl,jobr,id]=query[i];
jobl--;
while(jobl<l)
{
add(pre[--l]);
}
while(r<jobr)
{
add(pre[++r]);
}
while(l<jobl)
{
erase(pre[l++]);
}
while(jobr<r)
{
erase(pre[r--]);
}
ans[id]=sum;
}
for(int i=1;i<=m;i++)
{
cout<<ans[i]<<endl;
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
对于子数组异或问题,还是考虑转化成前缀异或。那么对于一个区间内的所有子数组,其必然可以从 l-1 到 r 的前缀和中得到。所以在扩窗口时,对于即将加入窗口的位置的前缀异或 x,其必然可以和当前窗口内所有前缀异或为 x^k 的位置组合产生贡献。注意,若 k=0,因为 x 就不能和自己一起产生贡献了,此时的贡献就变成从所有前缀异或为 x 的位置中选出 2 个的方案数。所以实现时,维护好每个前缀异或值的词频 cnts 以及当前窗口的贡献 sum。那么扩窗口和缩窗口时都按照这个逻辑考察前缀异或,先减去之前的贡献,再加上更新后的贡献即可。
二、带修改的莫队
还是直接在题里说吧。
1.数颜色 / 维护队列
傻逼卡常题......
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n,m;
scanf("%d%d",&n,&m);
int a[n+1];
for(int i=1;i<=n;i++)
{
scanf("%d",a+i);
}
array<int,4> query[m+1];
array<int,2> update[m+1];
int lenq=0,lenu=0;
for(int i=1;i<=m;i++)
{
char op[5];
int x,y;
scanf("%s%d%d",op,&x,&y);
if(op[0]=='Q')
{
lenq++;
query[lenq]={x,y,lenu,lenq};
}
else
{
update[++lenu]={x,y};
}
}
int blen=pow(n,0.666);
vector<int>bi(n+1);
for(int i=1;i<=n;i++)
{
bi[i]=(i-1)/blen+1;
}
sort(query+1,query+lenq+1,[&](auto &x,auto &y)
{
if(bi[x[0]]!=bi[y[0]])
{
return bi[x[0]]<bi[y[0]];
}
if(bi[x[1]]!=bi[y[1]])
{
return bi[x[1]]<bi[y[1]];
}
return x[2]<y[2];
});
const int MAXV=1e6;
vector<int>cnts(MAXV+1);
int kind=0;
auto erase=[&](int v)->void
{
if(--cnts[v]==0)
{
kind--;
}
};
auto add=[&](int v)->void
{
if(++cnts[v]==1)
{
kind++;
}
};
auto moveTime=[&](int jobl,int jobr,int time)->void
{
auto &[pos,val]=update[time];
if(jobl<=pos&&pos<=jobr)
{
erase(a[pos]);
add(val);
}
swap(a[pos],val);
};
vector<int>ans(lenq+1);
for(int l=1,r=0,t=0,i=1;i<=lenq;i++)
{
auto [jobl,jobr,jobt,id]=query[i];
while(jobl<l)
{
add(a[--l]);
}
while(r<jobr)
{
add(a[++r]);
}
while(l<jobl)
{
erase(a[l++]);
}
while(jobr<r)
{
erase(a[r--]);
}
while(t<jobt)
{
moveTime(jobl,jobr,++t);
}
while(jobt<t)
{
moveTime(jobl,jobr,t--);
}
ans[id]=kind;
}
for(int i=1;i<=lenq;i++)
{
printf("%d\n",ans[i]);
}
}
void init()
{
}
signed main()
{
//ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
考虑对单点修改操作打上修改时间点的信息,之后考虑对每个查询操作,统计其前面进行过多少次修改事件 jobt。在按一定策略排好序后,还是一条一条考察每个查询滑窗,每次都暴力修改到当前的 jobt 即可。唯一不同的是,在修改时执行 swap 操作将要改的数和原始的数交换。这样后续回退的时候 swap 回来即可。
对于排序策略,考虑先按 jobl 所在块号从小到大排序,再按 jobr 的块号从小到大排序,之后按 jobt 的数值从小到大排序。那么若块长为 x,则块数为 。此时对于左右边界所在块号,将其看作一个二元组。又因为块数为
,那么可能的组数就是
。那么对于同一组里的所有任务,其最多滑过所有时间,所以复杂度就是
的。又因为左右边界都在块内,所以左右边界的滑动规模就是
。所以对于一共的
组,整体复杂度就是
。对于组间代价,因为切换时左右边界和时间的变化最多就是
的,所以整体代价还是
的。所以整体代价
,将这个函数画出来可以发现其是一个单谷函数。所以对其求导计算导数为零的位置,可以得到在
取到最优复杂度
。
2.F. Machine Learning
很重要的一个 trick!!
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
#define popcount __builtin_popcount
using ll=long long;
using ld=long double;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n,q;
cin>>n>>q;
vector<int>a(n+1);
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
vector<array<int,4>>query(q+1);
vector<array<int,2>>update(q+1);
int lenq=0,lenu=0;
for(int i=1,op,l,r,p,x;i<=q;i++)
{
cin>>op;
if(op==1)
{
cin>>l>>r;
lenq++;
query[lenq]={l,r,lenu,lenq};
}
else
{
cin>>p>>x;
lenu++;
update[lenu]={p,x};
}
}
vector<int>sorted(n+lenu+1);
for(int i=1;i<=n;i++)
{
sorted[i]=a[i];
}
for(int i=1;i<=lenu;i++)
{
sorted[i+n]=update[i][1];
}
sort(sorted.begin()+1,sorted.end());
int len=1;
for(int i=1;i<=n+lenu;i++)
{
if(sorted[len]!=sorted[i])
{
sorted[++len]=sorted[i];
}
}
auto bs=[&](int v)->int
{
int l=1;
int r=len;
int m;
int ans;
while(l<=r)
{
m=l+r>>1;
if(sorted[m]>=v)
{
ans=m;
r=m-1;
}
else
{
l=m+1;
}
}
return ans;
};
for(int i=1;i<=n;i++)
{
a[i]=bs(a[i]);
}
for(int i=1;i<=lenu;i++)
{
update[i][1]=bs(update[i][1]);
}
int blen=pow(n,0.6666);
vector<int>bi(n+1);
for(int i=1;i<=n;i++)
{
bi[i]=(i-1)/blen+1;
}
sort(query.begin()+1,query.begin()+lenq+1,[&](auto &x,auto &y)
{
if(bi[x[0]]!=bi[y[0]])
{
return bi[x[0]]<bi[y[0]];
}
if(bi[x[1]]!=bi[y[1]])
{
return bi[x[1]]<bi[y[1]];
}
return x[2]<y[2];
});
vector<int>cnts(len+1);
vector<int>kind(len+1);
auto erase=[&](int v)->void
{
kind[cnts[v]]--;
cnts[v]--;
kind[cnts[v]]++;
};
auto add=[&](int v)->void
{
kind[cnts[v]]--;
cnts[v]++;
kind[cnts[v]]++;
};
auto move=[&](int jobl,int jobr,int time)->void
{
auto &[p,v]=update[time];
if(jobl<=p&&p<=jobr)
{
erase(a[p]);
add(v);
}
swap(a[p],v);
};
vector<int>ans(lenq+1);
for(int l=1,r=0,t=0,i=1;i<=lenq;i++)
{
auto [jobl,jobr,jobt,id]=query[i];
while(jobl<l)
{
add(a[--l]);
}
while(r<jobr)
{
add(a[++r]);
}
while(l<jobl)
{
erase(a[l++]);
}
while(jobr<r)
{
erase(a[r--]);
}
while(t<jobt)
{
move(jobl,jobr,++t);
}
while(jobt<t)
{
move(jobl,jobr,t--);
}
int mex=1;
while(mex<=n&&kind[mex]>0)
{
mex++;
}
ans[id]=mex;
}
for(int i=1;i<=lenq;i++)
{
cout<<ans[i]<<endl;
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
还是考虑定义 cnts[i] 为 i 这个数出现的词频,再定义 kind[i] 为词频为 i 的数出现了几种。那么在增加和删除数的时候,可以发现这两个数组的更新是很容易的。而对于查询操作,可以直接从 1 开始暴力枚举 kind[i]。因为若前 i 个词频的数都出现过,即使各出现了一次,那么数的个数也是呈等差数列增长的,所以枚举的代价是 的。又因为数字的范围很大,所以需要离散化。
第 inf 次怀念以前的 cf......
总结
能发明出这玩意儿的绝对是个人才,这个排序策略也太无敌了......