[bzoj3353]Archery
tips1:下标相关均默认在\([1,n]\)的环上
tips2:可能需要大量的感性理解(后面我也已经神智不清了)
设排名为\(x\),将\(\le x\)的编号为\(0\),\(>x\)的编号为\(1\)
记\(d_{i}\)为位置\(i\)上两人的编号和\(-[i>1]\),则每次从\(i\)移动到\(i-1\)的人编号为\([d_{i}\ge 1]\)
在此基础上,不断找到二元组\((i,j)\)满足以下两种情况之一:
-
\(d_{i}>0,d_{j}<0\)且\(d_{i-1}=...=d_{j+1}=0\)
-
\(i=1,d_{1}=2\)且\(j=\min_{j\ge 2,d_{j}\le 0}j\)(若不存在即所有人编号均为\(1\))
此时,经过\(\le n\)轮比赛会使得\(d_{i}-1,d_{j}+1\),且不影响其余位置,不妨在初始处理
当不存在\((i,j)\)时,\(d_{i}\)均同号(包含\(0\))且\(|d_{i}|\le 1\),进而每轮比赛即旋转\((\ge 0)\)或不变\((\le 0)\)
关于如何找\((i,j)\),维护两个栈\(st^{+}\)和\(st^{-}\),并逆序遍历\([1,n]\):
- 若\(d_{i}>0\),则在\(st^{+}\)中加入\(d_{i}\)个\(i\)
- 若\(d_{i}<0\),则在\(st^{+}\)中弹出\(-d_{i}\)个元素(与\(i\)配对),不足则在\(st^{-}\)中加入等量个\(i\)
最终,不断将\(st^{+}\)的栈顶和\(st^{-}\)的栈底配对,并特判\(d_{1}=2\)的情况
回到原问题,枚举\(x\)所在位置\(k\),对\(d_{k}\)是否\(+1\)分别求出最终的\(d\)序列
两者恰有一个位置不同,且该位置即为\(x\)的最终位置,时间复杂度为\(O(n^{2})\)
特判\(x=1\),显然最终答案为\(n\)(此时将\(d_{k}+1\)后会使得所有人编号均为\(1\))
最终\(d_{i}\ge 0\)和\(d_{i}\le 0\)取决于\(\sum_{i=1}^{n}d_{i}=(2n-x)-(n-1)<0\),化简得\(x>n+1\)
在此基础上,仅需求出"不存在\((i,j)\)时"两者不同的位置,最终位置即该位置\(-R\)或不变
对于第一类中的\((i,j)\),若\(k\not\in [j,i)\),则将\(d_{k}+1\)后仍满足其性质
将遍历顺序改为\(k,k-1,...,k+1\),则过程中选择的\((i,j)\)均满足\(k\not\in [j,i)\)
在此基础上,两者不同的位置有以下三种情况:
-
若\(x>n+1\),则\(|st^{+}|<|st^{-}|\)
当\(d_{k}+1\)后,\(k\)会与\(st^{-}\)栈顶底匹配,其余元素顺次向后匹配
最终,不同的位置即\(st^{-}\)栈底的第\(|st^{+}|+1\)个元素(额外匹配)
-
若\(x\le n+1\)且\(st^{-}\)为空,则仅需考虑\(d_{1}=2\)的情况
若\(k\ne \min_{j\ge 2,d_{j}\le 0}j\),则不同的位置即\(k\),否则即\(\min_{j>k,d_{j}\le 0}j\)
注意到后者与\(k\)无关(即\(\min_{i\ge 1,a_{i}<m}\lfloor\frac{i}{2}\rfloor+1\)),可以直接预处理
-
若\(x\le n+1\)且\(st^{-}\)不为空,类似第一种,不同的位置即\(st^{+}\)栈顶第\(|st^{-}|\)个元素(取消匹配)
另外,这总不会影响\(d_{1}=2\)时的\(j\),这可以分类讨论:
- 若\(\min_{j\ge 2,d_{j}\le 0}j<k\),即两次均选该位置
- 若\(\min_{j\ge 2,d_{j}\le 0}j\ge k\),由于\(d_{1}=2\)且最终\(st^{-}\)非空,必然已经匹配
#include<bits/stdc++.h>
using namespace std;
const int N=400005;
int n,m,R,pos,ans,a[N];
vector<int>v,V;//st+和st-
int query(int k){
v.clear(),V.clear();
for(int i=k;i;i--){
if (a[(i<<1)-1])v.push_back(i);
if ((i<k)&&(a[i<<1]))v.push_back(i);
if (i>1){
if (v.empty())V.push_back(i);
else v.pop_back();
}
}
for(int i=n;i>k;i--){
if (a[(i<<1)-1])v.push_back(i);
if (a[(i<<1)-2])v.push_back(i);
if (v.empty())V.push_back(i);
else v.pop_back();
}
if (m>n+1)return V[v.size()];
if (!V.empty())k=v[v.size()-V.size()];
else{
for(int i=1;i<(n<<1);i++)
if (!a[i]){
if (i>=(k<<1))k=(i>>1)+1;
break;
}
}
return (k-R%n+n-1)%n+1;
}
int main(){
scanf("%d%d%d",&n,&R,&m);
if (m==1){
printf("%d\n",n);
return 0;
}
for(int i=1;i<(n<<1);i++){
scanf("%d",&a[i]);
a[i]=(a[i]<m ? 0 : 1);
}
ans=n+1;
for(int i=n;i;i--){
int s=query(i);
if (s<ans)pos=i,ans=s;
}
printf("%d\n",pos);
return 0;
}
考虑分别处理出逆序遍历\([1,k]\)和\((k,n]\)后的结果,即可\(O(1)\)实现query,即
if (svp[k]<sVs[k+1]){
if (m>n+1){
if (svs[k+1]<sVp[k])return Vp[k][sVp[k]-svs[k+1]-1];
return Vs[k+1][svs[k+1]-sVp[k]+svp[k]];
}
return (vs[k+1][svs[k+1]-(sVp[k]+sVs[k+1]-svp[k])]-R%n+n-1)%n+1;
}
else{
if (m>n+1)return Vp[k][sVp[k]-(svp[k]-sVs[k+1]+svs[k+1])-1];
if (sVp[k]){
int s=svp[k]-sVs[k+1]+svs[k+1]-sVp[k];
if (s<svp[k]-sVs[k+1])k=vp[k][svp[k]-s-1];
else k=vs[k+1][s-(svp[k]-sVs[k+1])];
}
else{
if ((k<<1)<=mn)k=(mn>>1)+1;
}
return (k-R%n+n-1)%n+1;
}
(其中\(vp/Vp[k]\)表示\([1,k]\)的\(v/V\)翻转后,\(vs/Vs[k+1]\)表示\((k,n]\)的\(v/V\),\(s*\)表示\(*\)的大小)
关于\(vp/Vp\),分类讨论后,即
for(int i=2;i<=n;i++){
vp[i]=vp[i-1],Vp[i]=Vp[i-1];
if (a[(i<<1)-2]){
if (Vp[i].empty())vp[i].push_back(i-1);
else Vp[i].pop_back();
}
if (!a[(i<<1)-1])Vp[i].push_back(i);
}
关于\(vs/Vs\),分类讨论后,即
for(int i=n;i;i--){
vs[i]=vs[i+1],Vs[i]=Vs[i+1];
if (a[(i<<1)-1])vs[i].push_back(i);
if (a[(i<<1)-2])vs[i].push_back(i);
if (vs[i].empty())Vs[i].push_back(i);
else vs[i].pop_back();
}
逆序(指\([1,n]\))做一遍,存储每次vs[i].pop_back()的数后即可撤销
由于复杂度瓶颈在于拷贝,此时即可在线做,时间复杂度为\(O(n)\)
#include<bits/stdc++.h>
using namespace std;
const int N=400005;
int n,m,R,mn,svp,sVp,svs,sVs,pos,ans,a[N],z[N];
vector<int>vp,Vp,vs,Vs;
int query(int k){
if (svp<sVs){
if (m>n+1){
if (svs<sVp)return Vp[sVp-svs-1];
return Vs[svs-sVp+svp];
}
return (vs[svs-(sVp+sVs-svp)]-R%n+n-1)%n+1;
}
else{
if (m>n+1)return Vp[sVp-(svp-sVs+svs)-1];
if (sVp){
int s=svp-sVs+svs-sVp;
if (s<svp-sVs)k=vp[svp-s-1];
else k=vs[s-(svp-sVs)];
}
else{
if ((k<<1)<=mn)k=(mn>>1)+1;
}
return (k-R%n+n-1)%n+1;
}
return 0;
}
int main(){
scanf("%d%d%d",&n,&R,&m);
if (m==1){
printf("%d\n",n);
return 0;
}
for(int i=1;i<(n<<1);i++){
scanf("%d",&a[i]);
a[i]=(a[i]<m ? 0 : 1);
}
for(int i=1;i<(n<<1);i++)
if (!a[i]){mn=i;break;}
for(int i=n;i;i--){
if (a[(i<<1)-1])svs++,vs.push_back(i);
if (a[(i<<1)-2])svs++,vs.push_back(i);
z[i]=(svs ? vs.back() : 0);
if (!svs)sVs++,Vs.push_back(i);
else svs--,vs.pop_back();
}
ans=n+1;
for(int i=1;i<=n;i++){
if (i==1){
if (a[1])svp++,vp.push_back(1);
}
else{
if (a[(i<<1)-2]){
if (!sVp)svp++,vp.push_back(i-1);
else sVp--,Vp.pop_back();
}
if (!a[(i<<1)-1])sVp++,Vp.push_back(i);
}
if (z[i])svs++,vs.push_back(z[i]);
else sVs--,Vs.pop_back();
if (a[(i<<1)-1])svs--,vs.pop_back();
if (a[(i<<1)-2])svs--,vs.pop_back();
int s=query(i);
if (s<=ans)pos=i,ans=s;
}
printf("%d\n",pos);
return 0;
}