数据结构阶段测试
A
预计:100,实际:100。
平衡树维护之即可。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=4e5+5;
int m,q,root,tot;
struct TREE{
int ls,rs,siz,rnk,val;
}tree[N];
int addnode(int val){
tree[++tot].val=val;
tree[tot].siz=1;
tree[tot].rnk=rand();
tree[tot].ls=tree[tot].rs=0;
return tot;
}
void pushup(int k){
tree[k].siz=tree[tree[k].ls].siz+tree[tree[k].rs].siz+1;
}
void split(int k,int &a,int &b,int val){
if(!k){
a=b=0;
return;
}
if(tree[k].val<=val)
a=k,split(tree[k].rs,tree[k].rs,b,val);
else
b=k,split(tree[k].ls,a,tree[k].ls,val);
pushup(k);
}
void merge(int &k,int a,int b){
if(!a||!b){
k=a+b;
return;
}
if(tree[a].rnk<tree[b].rnk)
k=a,merge(tree[k].rs,tree[k].rs,b);
else
k=b,merge(tree[k].ls,a,tree[k].ls);
pushup(k);
}
void ins(int &k,int val){
int a=0,b=0,cur=addnode(val);
split(k,a,b,val);
merge(a,a,cur);
merge(k,a,b);
}
int fndrnk(int k,int x){
while(tree[tree[k].ls].siz+1!=x){
if(tree[tree[k].ls].siz>=x)
k=tree[k].ls;
else
x-=tree[tree[k].ls].siz+1,k=tree[k].rs;
}
return tree[k].val;
}
signed main(){
//freopen("A.in","r",stdin);
//freopen("A.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0);
srand(time(0));
cin>>m>>q;
for(int i=1,x;i<=m;i++)
cin>>x,ins(root,x);
while(q--){
int op,x;
cin>>op>>x;
if(op==1)
cout<<fndrnk(root,tree[root].siz-x+1)<<'\n';
else
ins(root,x);
}
return 0;
}
B
预计:30,实际:88。
经典题,赛时只想到了暴力跳。
其实值域分块一下,每次按块跳,跳到有空位的块(这个,另开一个桶维护之)就暴力查询即可。
实现
//
// P4137.cpp
//
//
// Created by _XOFqwq on 2025/4/26.
//
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
#define int long long
using namespace std;
const int N=2e5+5;
int n,m,t,tt;
int a[N],c[N],cnt[N],ans[N];
int L[N],R[N],pos[N];
struct Q {
int l,r,id;
}q[N];
bool cmp(Q &x,Q &y){
return (x.l/tt!=y.l/tt?x.l<y.l:((x.l/tt)&1?x.r<y.r:x.r>y.r));
}
void add(int x){
if (!c[a[x]]) {
cnt[pos[a[x]]]++;
}
c[a[x]]++;
}
void del(int x){
c[a[x]]--;
if (!c[a[x]]) {
cnt[pos[a[x]]]--;
}
}
signed main(){
//freopen("P4137_1.in","r",stdin);
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>m;
for (int i=1; i<=n; i++) {
cin>>a[i];
}
for (int i=1; i<=m; i++) {
cin>>q[i].l>>q[i].r,q[i].id=i;
}
tt=sqrt(n);
sort(q+1,q+m+1,cmp);
t=sqrt(N);
L[1]=0,R[1]=sqrt(N)-1;
for (int i=2; i<=t; i++) {
L[i]=R[i-1]+1,R[i]=L[i]+sqrt(N)-1;
}
if (R[t]<N) {
t++,L[t]=R[t-1]+1,R[t]=N;
}
for (int i=1; i<=t; i++) {
for (int j=L[i]; j<=R[i]; j++) {
pos[j]=i;
}
}
int x=1,y=0;
for (int i=1; i<=m; i++) {
int qx=q[i].l,qy=q[i].r;
for (; x>qx; add(--x)) {}
for (; y<qy; add(++y)) {}
for (; x<qx; del(x++)) {}
for (; y>qy; del(y--)) {}
int tot=0;
for (int j=1; j<=t; j++) {
if (cnt[j]<R[j]-L[j]+1) {
for (int k=L[j]; k<=R[j]; k++) {
if (!c[k]) {
tot=k; break;
}
}
break;
}
}
ans[q[i].id]=tot;
}
for (int i=1; i<=m; i++) {
cout<<ans[i]<<'\n';
}
return 0;
}
总结:把每个 idea 记录下来,进行深度思考。
C
预计:10,实际:10。
只有询问,考虑莫队。
对于一个可重排为回文串的串,它必须满足出现奇数次的字母的个数不超过 \(1\),这样子是可以暴力统计的。赛时止步于此。
其实看到字符集就想到了状压,将每个字母的出现次数压成一个二进制数。
具体地,我们令 \(a_i\) 表示第 \(i\) 个字母的二进制值,则 \(a_i=2^{s_i}\)。
看到要统计子区间个数,于是想到运用 CF 某题(具体忘了,反正是有次的作业题)的套路:维护前缀异或值 \(sum_i\),枚举合法子区间状态,得到另一个端点的合法状态,然后统计合法的另一个端点个数(桶维护之)即可。
实现
//
// P3604.cpp
//
//
// Created by _XOFqwq on 2025/4/26.
//
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <stdlib.h>
#include <vector>
#include <queue>
#include <cmath>
#include <stack>
#include <map>
#include <set>
#define int long long
using namespace std;
const int N=1e5+5,M=(1<<26)+5;
int n,m,tot,t;
int sum[N],cnt[M],ans[N];
struct Q {
int l,r,id;
}q[N];
bool cmp(Q &x,Q &y){
return (x.l/t!=y.l/t?x.l<y.l:((x.l/t)&1?x.r<y.r:x.r>y.r));
}
void add(int x){
tot+=cnt[sum[x]];
for (int i=0; i<26; i++) {
tot+=cnt[sum[x]^(1<<i)];
}
cnt[sum[x]]++;
}
void del(int x){
cnt[sum[x]]--;
tot-=cnt[sum[x]];
for (int i=0; i<26; i++) {
tot-=cnt[sum[x]^(1<<i)];
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>m,t=sqrt(n);
for (int i=1; i<=n; i++) {
char c; cin>>c;
sum[i]=sum[i-1]^(1<<(c-'a'));
}
for (int i=1; i<=m; i++) {
cin>>q[i].l>>q[i].r,q[i].l--,q[i].id=i;
}
sort(q+1,q+m+1,cmp);
int x=1,y=0;
for (int i=1; i<=m; i++) {
int qx=q[i].l,qy=q[i].r;
for (; x>qx; add(--x)) {}
for (; y<qy; add(++y)) {}
for (; x<qx; del(x++)) {}
for (; y>qy; del(y--)) {}
ans[q[i].id]=tot;
}
for (int i=1; i<=m; i++) {
cout<<ans[i]<<'\n';
}
return 0;
}
总结:字符集->状压、统计合法子区间个数->维护前缀异或值。
整体复盘:
成绩:\(rk \ 15 \ 198pts\)。
其实都是上课讲过的套路,说明理解的不够透彻,并且深度思考不够,需要加强训练。