第 45 届国际大学生程序设计竞赛(ICPC)亚洲区域赛(昆明)M(可持久化权值线段树+思维暴力)
题意简述:
有$n$堆石子,$Rika$ 和 $Satoko$两人根据这$n$堆石子玩一个游戏,$Rika$在这$n$堆石子里面选择连续的几堆石子,然后要求$Satoko$写下一个数量,如果这个数量$Rika$无法用这连续几堆石子任意组成的话 (子序列) 那么$Rika$胜利,否则$Satoko$胜利,求$Satoko$写下的最小的数使得其胜利。那么这题就转化成了MEX问题。求这连续的几堆石子所有构造数的MEX是什么。
思维部分:
首先设 $Sn=A[1]+A[2]+A[3]+…+A[n]$,使得$Xn=Sn+1$,$A[1]$ ~ $A[n]$是从小到大互不相同的数($A[i]$数量不限至于$1$)。假使$A[1]$ ~ $A[n]$可以构造出$1$ ~ $Sn$中任意一个数,那么如果$A[n+1]>$$Xn$那么答案即是$Xn$因为后面的数全部大于$Xn$,必然无法构成$Xn$。否则求出$Sn+1=A[1]+A[2]+A[3]+…+A[n]+A[n+1]$,同样$A[1]$ ~ $A[n+1]$可以构成$1$ ~ $S[n+1]$内任意的数,继续往下求直至发现无法构成的数。
算法部分:
显然,要求从小到大的互不相同数时,我们可以想到的是可持久化权值线段树来维护数大小之间的相对关系。和经典主席树入门题:第K小数 有着相同的维护操作,只是将第K小数中维护的每个数出现次数变成了下标从$0$到所求下标$x$之内所有数的和,所以我们改为维护每个下标值的$sum$,即出现次数 $*$ 下标数本身大小。剩余相对较难部分我认为即暴力求出最小的构造不了的数。时间复杂度是$O(logn)$,因为每次的判断是一个倍增的过程。
实现代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long//谨慎使用,这题是因为内存给的很大
int n,q;
const int N = 1e6+10;
int a[N];
int root[N];//可持久化的历史状态的根节点
vector<int>vec;
int cnt;
struct Node{
int l,r;
int sum;//sum维护每个idx上数的总和
}tr[N*4+N*21];//N*4+NlogN
int build(int l,int r){//第K小数的板子
int p=++cnt;
if(l==r)return p;
int mid=l+r>>1;
tr[p].l=build(l,mid);
tr[p].r=build(mid+1,r);
return p;
}
int find(int x){//二分
return lower_bound(vec.begin(),vec.end(),x)-vec.begin();
}
int insert(int pre,int l,int r,int x){//也类似于第K小数板子
int p=++cnt;
tr[p]=tr[pre];//指向上一个状态
if(l==r){
tr[p].sum+=vec[x];
return p;
}
int mid=l+r>>1;
if(x<=mid)tr[p].l=insert(tr[p].l,l,mid,x);
else tr[p].r=insert(tr[p].r,mid+1,r,x);
tr[p].sum=tr[tr[p].l].sum+tr[tr[p].r].sum;
return p;
}
int query(int pre,int now,int l,int r,int x){//相对于第K小数变动一点,因为此处要求的前缀值
if(r<=x)return tr[now].sum-tr[pre].sum;
int mid=l+r>>1;
if(x<=mid)return query(tr[pre].l,tr[now].l,l,mid,x);
else return query(tr[pre].l,tr[now].l,l,mid,mid)+query(tr[pre].r,tr[now].r,mid+1,r,x);
}
signed main(){
cin>>n>>q;
for(int i=1;i<=n;i++){
cin>>a[i];
vec.push_back(a[i]);
}
sort(vec.begin(),vec.end());
vec.erase(unique(vec.begin(),vec.end()),vec.end());//离散化
root[0]=build(0,vec.size()-1);
for(int i=1;i<=n;i++){
root[i]=insert(root[i-1],0,vec.size()-1,find(a[i]));//在0~n-1的范围内在find(a[i])上插入
}
int ans=0;
while(q--){
int l,r;
cin>>l>>r;
int l1,r1;
l1=(l+ans)%n+1;
r1=(r+ans)%n+1;
l=min(l1,r1);
r=max(l1,r1);
ans=0;
int pre=-1;//如果没有1的话第一次x是-1直接返回,有1存在x是0。
while(1){
int x=upper_bound(vec.begin(),vec.end(),ans+1)-vec.begin();//找第一个大于ans+1的下标
x--;
if(x==pre)break;
ans=query(root[l-1],root[r],0,vec.size()-1,x);//更新ans为最大值,同时消除l-1的影响
pre=x;
}
ans++;
cout<<ans<<endl;
}
return 0;
}
您您您
您!