题解:P10264 [GESP202403 八级]接竹竿
hgckythgcfhk · · 题解
来自搬题人的题解。
形式化题意:定义一个序列的权值为,维护一个栈,使得每次从序列中取一个元素,如果这个元素在栈中,弹栈弹到这个元素不在栈中为止,否则加入这个栈,最后栈中的元素个数,现在给定一个序列,多次询问这个序列一个子段的权值。
这个形式化题意并没有比题面简化多少,但已经把暴力的写法说清楚了,现在考虑优化,观察下面这个序列:
11 1 2 3 4 5 6 7 8 9 10 1 12
发现两个
这样时间复杂度是不对的,我们改变一下对
有人可能会问,为什么不直接跳到最后一个,我跳到最后一个赛时过了,来,我给你看组 hack。
1
8
1 2 3 1 2 3 1 2
1
1 8
手动模拟一下,答案为
这是官方的 std。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+10;
int a[N];
int nxt[N][30],pos[20];
int main(){
int t;
cin>>t;
while(t--){
int n;
cin>>n;
memset(pos,0,sizeof pos);
for(int i=1;i<=n;i++){
cin>>a[i];
for(int j=0;j<=20;j++)nxt[i][j]=n+1;
}
for(int i=n;i>=1;i--){
if(!pos[a[i]]){
nxt[a[i]][0]=n+1;
pos[a[i]]=i;
}else{
nxt[i][0]=pos[a[i]];
pos[a[i]]=i;
}
}
for(int i=n;i>=1;i--){
for(int j=1;j<=20;j++){
if(nxt[i][j-1]+1<=n)
nxt[i][j]=nxt[nxt[i][j-1]+1][j-1];
}
}
int q;
cin>>q;
while(q--){
int l,r;
cin>>l>>r;
int ii=l;
int ans=0;
while(ii<=r){
while(ii<=r&&nxt[ii][0]>r){
ii++;
ans++;
}
if(ii>r)break;
for(int j=20;j>=0;j--){
if(nxt[ii][j]<=r){
ii=nxt[ii][j];
break;
}
}
ii++;
}
cout<<ans<<"\n";
}
}
}
注意,官方的 std 中第20行应为 nxt[i][0]=n+1; ,而不是 nxt[a[i]][0]=n+1;。
当然,也可以直接把这一行删掉,具体见这个帖子。
感谢 @sapo1o 指出问题和 @sbh2012 提供的 hack。请大家不要盲目相信官方的 std,从这个细节可以看出,官方在写这份程序的时候思路很乱,理由是前面已完成了初始化,所以这行程序没有任何作用。
不过,由于较大且相对比较随机,经测试,保证正确的程序可以通过,可以证明,这个错误在大的随即数据下几乎不可能出错,所以目前未造成影响,不过这也是一件比较可怕的事情,CCF 的官方数据也是这份 std 造的,我没有修改任何一个字符,所以官方数据的正确性和这次 GESP 的成绩值得怀疑,作为 CCF 举办的官方比赛,我认为这件事情的影响重大且极其恶劣,望周知!