第 45 届国际大学生程序设计竞赛(ICPC)亚洲区域赛(昆明)M(可持久化权值线段树+思维暴力)
题意简述:
有
n
n
n堆石子,
R
i
k
a
Rika
Rika 和
S
a
t
o
k
o
Satoko
Satoko两人根据这
n
n
n堆石子玩一个游戏,
R
i
k
a
Rika
Rika在这
n
n
n堆石子里面选择连续的几堆石子,然后要求
S
a
t
o
k
o
Satoko
Satoko写下一个数量,如果这个数量
R
i
k
a
Rika
Rika无法用这连续几堆石子任意组成的话 (子序列) 那么
R
i
k
a
Rika
Rika胜利,否则
S
a
t
o
k
o
Satoko
Satoko胜利,求
S
a
t
o
k
o
Satoko
Satoko写下的最小的数使得其胜利。那么这题就转化成了MEX问题。求这连续的几堆石子所有构造数的MEX是什么。
思维部分:
首先设
S
n
=
A
[
1
]
+
A
[
2
]
+
A
[
3
]
+
.
.
.
+
A
[
n
]
Sn=A[1]+A[2]+A[3]+...+A[n]
Sn=A[1]+A[2]+A[3]+...+A[n],使得
X
n
=
S
n
+
1
Xn=Sn+1
Xn=Sn+1,
A
[
1
]
A[1]
A[1] ~
A
[
n
]
A[n]
A[n]是从小到大的互不相同的数(
A
[
i
]
A[i]
A[i]的数量不局限于
1
1
1)。假使
A
[
1
]
A[1]
A[1] ~
A
[
n
]
A[n]
A[n]可以构造出
1
1
1 ~
S
n
Sn
Sn中任意一个数,那么如果
A
[
n
+
1
]
>
A[n+1]>
A[n+1]>
X
n
Xn
Xn那么答案即是
X
n
Xn
Xn因为后面的数全部大于
X
n
Xn
Xn,必然无法构成
X
n
Xn
Xn。否则求出
S
n
+
1
=
A
[
1
]
+
A
[
2
]
+
A
[
3
]
+
.
.
.
+
A
[
n
]
+
A
[
n
+
1
]
Sn+1=A[1]+A[2]+A[3]+...+A[n]+A[n+1]
Sn+1=A[1]+A[2]+A[3]+...+A[n]+A[n+1],同样
A
[
1
]
A[1]
A[1] ~
A
[
n
+
1
]
A[n+1]
A[n+1]可以构成
1
1
1 ~
S
[
n
+
1
]
S[n+1]
S[n+1]内任意的数,继续往下求直至发现无法构成的数。
算法部分:
显然,要求从小到大的互不相同数时,我们可以想到的是可持久化权值线段树来维护数大小之间的相对关系。和经典主席树入门题:第K小数 有着相同的维护操作,只是将第K小数中维护的每个数出现次数变成了下标从
0
0
0到所求下标
x
x
x之内所有数的和,所以我们改为维护每个下标值的
s
u
m
sum
sum,即出现次数
?
*
? 下标数本身大小。剩余相对较难部分我认为即暴力求出最小的构造不了的数。时间复杂度是
O
(
l
o
g
n
)
O(logn)
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;
}tr[N*4+N*21];
int build(int l,int r){
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){
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){
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]));
}
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;
while(1){
int x=upper_bound(vec.begin(),vec.end(),ans+1)-vec.begin();
x--;
if(x==pre)break;
ans=query(root[l-1],root[r],0,vec.size()-1,x);
pre=x;
}
ans++;
cout<<ans<<endl;
}
return 0;
}
|