2025.5.12-2025.5.17做题记录
前言
感觉很久没写了,最近在楼下认真学文化课上来学 OI 感觉没时间没精力了,这周做题好少。
至少我们在向好的方向前进,对吗?已经半个月没有感受到这周的状态了。
题目列表
排序
第一眼,这不是 simple task 吗?怎么还紫了。
第二眼,不是哥们你值域怎么 1e5 啊,老实了。
发现自己只会 30% 分的暴力。
正解: 不是哥们这也能二分啊!
首先我们不会 \(O(\log n)\) 的平凡数组排序,如果你会,请申请图灵奖。
但是我们可以做到 \(O(\log n)\) 的 01 串排序。
具体而言,记录区间 \(1\) 的个数,拿线段树把前一段赋值 \(0\),后一段赋值 \(1\) 。复杂度上界竟然是读入与建树 。
我们去二分这个 \(q\) 位置可以填 \(x\),然后将 \(a_i\ge x\) 赋值 \(1\),其余为 \(0\) 。
然后就是正常线段树操作了。
二分的一百个应用!
点击查看代码
#include<iostream>
#define ls u*2
#define rs u*2+1
using namespace std;
const int N=1e5+10;
int n,m,k,a[N];
struct node{
int op,l,r;
}q[N];
int tr[4*N],tag[4*N];
void pushup(int u){
tr[u]=tr[ls]+tr[rs];
}
void upd(int u,int k,int len){
tr[u]=k*len;
tag[u]=k;
}
void pushdown(int u,int l,int r){
if(tag[u]==-1) return ;
int mid=(l+r)>>1;
upd(ls,tag[u],mid-l+1);
upd(rs,tag[u],r-mid);
tag[u]=-1;
}
void build(int u,int l,int r,int x){
tag[u]=-1;
if(l==r){
tr[u]=(a[l]>=x);
return ;
}
int mid=(l+r)>>1;
build(ls,l,mid,x);build(rs,mid+1,r,x);
pushup(u);
}
void modify(int u,int l,int r,int x,int y,int k){
if(l>=x && r<=y){
upd(u,k,r-l+1);
return ;
}
pushdown(u,l,r);
int mid=(l+r)>>1;
if(x<=mid) modify(ls,l,mid,x,y,k);
if(mid<y) modify(rs,mid+1,r,x,y,k);
pushup(u);
}
int query(int u,int l,int r,int x,int y){
if(l>=x && r<=y){
return tr[u];
}
pushdown(u,l,r);
int res=0;
int mid=(l+r)>>1;
if(x<=mid) res+=query(ls,l,mid,x,y);
if(mid<y) res+=query(rs,mid+1,r,x,y);
return res;
}
int ask(int u,int l,int r,int x){
if(l==x && r==x){
return tr[u];
}
pushdown(u,l,r);
int mid=(l+r)>>1;
if(x<=mid) return ask(ls,l,mid,x);
else return ask(rs,mid+1,r,x);
}
bool check(int x){
build(1,1,n,x);
for(int i=1;i<=m;i++){
int l=q[i].l,r=q[i].r;
int cnt=query(1,1,n,l,r);
if(cnt==0) continue;
if(q[i].op==0){
modify(1,1,n,r-cnt+1,r,1);
modify(1,1,n,l,r-cnt,0);
}else{
modify(1,1,n,l,l+cnt-1,1);
modify(1,1,n,l+cnt,r,0);
}
}
return ask(1,1,n,k);
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=m;i++){
cin>>q[i].op>>q[i].l>>q[i].r;
}
cin>>k;
int l=1,r=n;
while(l<=r){
int mid=(l+r)>>1;
if(check(mid)){
l=mid+1;
}else{
r=mid-1;
}
}
cout<<r;
return 0;
}
等这场战争结束之后
其实很一眼了,可撤销并查集+值域分块。但是!我不会写这两个的结合体qwq
具体做法:离线建立版本树,离散化后值域分块维护每一块每个值出现次数,入树时进行修改或询问,出树时撤销修改。
然后就是 Ynoi 的经典卡常时刻,本题需要卡的是 20MB 的空间。
- 值域分块数组可以用 short。
- 块长设大一点。
- 使用版本树,别写在线!
点击查看代码
#include<iostream>
#include<vector>
#include<algorithm>
#define re register
using namespace std;
const int N=1e5+3;
const int len=3000;
int n,m,k;
int tot;
short sum[N][N/len+2];
int fa[N],siz[N];
int a[N],b[N],num[N],cnt[N],ans[N];
struct Edge{
int to,nxt;
}e[N];
int head[N],hd;
void add(int u,int v){
e[++hd].to=v;
e[hd].nxt=head[u];
head[u]=hd;
return ;
}
struct question{
int op,x,y;
}q[N];
int find(int x){return fa[x]==x ? x : find(fa[x]);}
inline void merge(int &fx,int &fy){
if(fx==fy) return ;
if(siz[fx]<=siz[fy]) swap(fx,fy);
siz[fx]+=siz[fy];
fa[fy]=fx;
for(re int i=1;i<=tot;i++) sum[fx][i]+=sum[fy][i];
return ;
}
void dfs(int u){
int op=q[u].op,x=q[u].x,y=q[u].y;
int fx=0,fy=0;
if(op!=2) fx=find(x),fy=find(y);
if(op==1){
merge(fx,fy);
}
if(op==3){
if(y>siz[fx]) ans[u]=-1;
else{
for(re int i=1;i<=tot;i++){
if(y<=sum[fx][i]){
for(re int j=(i-1)*len+1;j<=i*len;j++){
if(find(num[j])==fx){
y--;
if(y==0){
ans[u]=b[j];
break;
}
}
}
break;
}
y-=sum[fx][i];
}
}
}
for(re int i=head[u];i;i=e[i].nxt) dfs(e[i].to);
if(op==1 && fx!=fy){
siz[fx]-=siz[fy];
fa[fy]=fy;
for(int i=1;i<=tot;i++) sum[fx][i]-=sum[fy][i];
}
}
int main(){
cin>>n>>m;
tot=(n-1)/len+1;
for(re int i=1;i<=n;i++){
fa[i]=i;siz[i]=1;
cin>>a[i];
b[i]=a[i];
}
sort(b+1,b+1+n);
for(re int i=1;i<=n;i++){
a[i]=lower_bound(b+1,b+1+n,a[i])-b;
a[i]+=cnt[a[i]]++;
num[a[i]]=i;
sum[i][(a[i]-1)/len+1]=1;
}
for(re int i=1;i<=m;i++){
cin>>q[i].op;
if(q[i].op==1){
cin>>q[i].x>>q[i].y;
}else if(q[i].op==2){
cin>>q[i].x;
add(q[i].x,i);
continue;
}else if(q[i].op==3){
cin>>q[i].x>>q[i].y;
}
add(i-1,i);
}
dfs(0);
for(re int i=1;i<=m;i++){
if(q[i].op==3){
cout << ans[i] << '\n';
}
}
return 0;
}
Dynamic Rankings
没任何营养的树套树板子,直接写就是了。
点击查看代码
#include<iostream>
#include<algorithm>
using namespace std;
const int N=5e5+50;
const int inf=2147483647;
struct node{
int val,ls,rs;
}tr[N*100];
struct mdf{
int b,c,d;
char op;
}q[N];
int rt[N],tem[N],tmp[N],cnt,num,tot;
int n,m,lsh[2*N],len,a[N];
int find(int x){
return lower_bound(lsh+1,lsh+1+len,x)-lsh;
}
int lowbit(int x){
return x&-x;
}
void pushup(int u){
tr[u].val=tr[tr[u].ls].val+tr[tr[u].rs].val;
return ;
}
void modify(int &u,int l,int r,int x,int k){
if(!u) u=++tot;
if(l==r){
tr[u].val+=k;
return ;
}
int mid=(l+r)>>1;
if(x<=mid) modify(tr[u].ls,l,mid,x,k);
else modify(tr[u].rs,mid+1,r,x,k);
pushup(u);
return ;
}
void add(int p,int k){
for(int i=p;i<=n;i+=lowbit(i)) modify(rt[i],1,len,a[p],k);
}
int query1(int l,int r,int k){
if(l==r){
return l;
}
int mid=(l+r)>>1,sum=0;
for(int i=1;i<=cnt;i++) sum+=tr[tr[tem[i]].ls].val;
for(int i=1;i<=num;i++) sum-=tr[tr[tmp[i]].ls].val;
if(k<=sum){
for(int i=1;i<=cnt;i++) tem[i]=tr[tem[i]].ls;
for(int i=1;i<=num;i++) tmp[i]=tr[tmp[i]].ls;
return query1(l,mid,k);
}else{
for(int i=1;i<=cnt;i++) tem[i]=tr[tem[i]].rs;
for(int i=1;i<=num;i++) tmp[i]=tr[tmp[i]].rs;
return query1(mid+1,r,k-sum);
}
}
int fnum(int l,int r,int k){
cnt=num=0;
for(int i=r;i;i-=lowbit(i)){
tem[++cnt]=rt[i];
}
for(int i=l-1;i;i-=lowbit(i)){
tmp[++num]=rt[i];
}
return query1(1,len,k);
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
tot=cnt=num=0;
for(int i=1;i<=n;i++){
cin>>a[i];
lsh[++len]=a[i];
}
for(int i=1;i<=m;i++){
cin>>q[i].op>>q[i].b>>q[i].c;
if(q[i].op=='Q') cin>>q[i].d;
else lsh[++len]=q[i].c;
if(q[i].op=='Q') lsh[++len]=q[i].d;
}
sort(lsh+1,lsh+1+len);
len=unique(lsh+1,lsh+1+len)-lsh-1;
for(int i=1;i<=n;i++){
a[i]=find(a[i]);
add(i,1);
}
lsh[0]=-inf;
lsh[len+1]=inf;
for(int i=1;i<=m;i++){
if(q[i].op=='Q'){
cout<<lsh[fnum(q[i].b,q[i].c,q[i].d)]<<'\n';
}
if(q[i].op=='C'){
add(q[i].b,-1);
a[q[i].b]=find(q[i].c);
add(q[i].b,1);
}
}
return 0;
}
[Violet] 蒲公英
自然的想到离散化值域分块,因为求众数,要求多少个相同数,所以考虑前缀和。
具体而言,维护两个分块,一个 \(s_{i,j}\) 表示第 \(i\) 个块内的每个数的个数前缀和(对于 \(i\) 统计),一个 \(f_{i,j}\) 表示第 \(i\) 块与第 \(j\) 块之间的众数。这两个数组可以预处理。
对于询问,我们将询问分成三部分:
\(———l——bl———————br——r————\)
其中 \(l,r\) 表示询问区间,\(bl,br\) 表示若干个整块。可能的众数集合是:整块内的众数,\(l\to bl\) 之间的某数,\(br\to r\) 之间的某数。
散块暴力,整块一起,询问区间不能被分为两块时暴力,那么我们就做完了。
具体实现见代码(因为我也是看的题解写的代码)
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=4e4+10;
int n,m;
int a[N],b[N];
int ans;
int len,tot;
int f[210][210],s[210][N];
int t[N];
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
len=int(sqrt(n));
tot=(n-1)/len+1;
for(int i=1;i<=n;i++){
cin>>a[i];
b[i]=a[i];
}
sort(b+1,b+1+n);
int sum=unique(b+1,b+1+n)-b-1;
for(int i=1;i<=n;i++){
a[i]=lower_bound(b+1,b+1+sum,a[i])-b;
}
for(int i=1;i<=tot;i++){
for(int j=(i-1)*len+1;j<=min(i*len,n);j++){
s[i][a[j]]++;
}
for(int j=1;j<=sum;j++){
s[i][j]+=s[i-1][j];
}
}
for(int i=1;i<=tot;i++){
for(int j=i;j<=tot;j++){
int mx=f[i][j-1];
for(int k=(j-1)*len+1;k<=min(len*j,n);k++){
if((s[j][a[k]]-s[i-1][a[k]]>s[j][mx]-s[i-1][mx]) || (s[j][a[k]]-s[i-1][a[k]]==s[j][mx]-s[i-1][mx] && a[k]<mx))
mx=a[k];
}
f[i][j]=mx;
}
}
while(m--){
int l,r;
cin>>l>>r;
l=(l+ans-1)%n+1;
r=(r+ans-1)%n+1;
if(l>r) l^=r^=l^=r;
int bl=(l-1)/len+1,br=(r-1)/len+1,mx=0;
if(br-bl<=1){
for(int i=l;i<=r;i++) t[a[i]]++;
for(int i=l;i<=r;i++){
if(t[a[i]]>t[mx] || (t[a[i]]==t[mx] && a[i]<mx)) mx=a[i];
}
for(int i=l;i<=r;i++) t[a[i]]=0;
}else{
for(int i=l;i<=len*bl;i++){
t[a[i]]++;
}
for(int i=(br-1)*len+1;i<=r;i++){
t[a[i]]++;
}
mx=f[bl+1][br-1];
for(int i=l;i<=len*bl;i++){
int old=t[mx]+s[br-1][mx]-s[bl][mx];
int now=t[a[i]]+s[br-1][a[i]]-s[bl][a[i]];
if(now>old || (now==old && a[i]<mx)) mx=a[i];
}
for(int i=(br-1)*len+1;i<=r;i++){
int old=t[mx]+s[br-1][mx]-s[bl][mx];
int now=t[a[i]]+s[br-1][a[i]]-s[bl][a[i]];
if(now>old || (now==old && a[i]<mx)) mx=a[i];
}
for(int i=l;i<=len*bl;i++){
t[a[i]]=0;
}
for(int i=(br-1)*len+1;i<=r;i++){
t[a[i]]=0;
}
}
ans=b[mx];
cout<<ans<<'\n';
}
return 0;
}
LCA
有点巧妙。
考虑求 LCA 的过程,让 \(z\) 点一直上跳,直到跳到与 \(i\) 点到根节点的路径重合。
所以我们考虑将 \(l,r\) 区间的所有点打标记,然后 \(z\) 经过一个标记点就有一个 \(dep_{\text{标记点}}\) 的贡献。
然后时间复杂度不太好看,考虑将询问按端点升序离线,逐渐从 \(1\) 加点到 \(n\),扫到询问的节点就统计 \(z\) 与当前情况的贡献。
具体的,扫到左端点统计个负贡献,扫到右端点统计个正贡献,答案是两者相减。
更具体的见代码:
点击查看代码
#include<bits/stdc++.h>
#define ls u*2
#define rs u*2+1
using namespace std;
const int N=1e5+10;
const int p=201314;
int n,m;
struct Q{
int id,x,z;
bool f;
}q[N<<1];
vector<int> vec[N];
int cnt=0;
bool cmp(Q a,Q b){
return a.x<b.x;
}
struct Ans{
int a1,a2;
}ans[N];
int tr[N<<2],tag[N<<2];
int dfn[N],dfx,top[N],dep[N],fa[N],siz[N],hson[N];
void dfs1(int u,int f){
fa[u]=f;
siz[u]=1;
dep[u]=dep[f]+1;
for(int v:vec[u]){
if(v==f) continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[hson[u]]<siz[v]) hson[u]=v;
}
}
void dfs2(int u,int tp){
dfn[u]=++dfx;
top[u]=tp;
if(!hson[u]) return ;
dfs2(hson[u],tp);
for(int v:vec[u]){
if(v==fa[u] || v==hson[u]) continue;
dfs2(v,v);
}
}
void pushup(int u){
tr[u]=(tr[ls]+tr[rs])%p;
}
void upd(int u,int k,int len){
tr[u]=(tr[u]+k*len)%p;
tag[u]=(tag[u]+k)%p;
}
void pushdown(int u,int l,int r){
int mid=(l+r)>>1;
upd(ls,tag[u],mid-l+1);
upd(rs,tag[u],r-mid);
tag[u]=0;
}
void modify(int u,int l,int r,int x,int y,int k){
if(l>=x && r<=y){
upd(u,k,r-l+1);
return ;
}
pushdown(u,l,r);
int mid=(l+r)>>1;
if(x<=mid) modify(ls,l,mid,x,y,k);
if(mid<y) modify(rs,mid+1,r,x,y,k);
pushup(u);
}
int query(int u,int l,int r,int x,int y){
if(l>=x && r<=y) return tr[u];
int res=0;
int mid=(l+r)>>1;
pushdown(u,l,r);
if(x<=mid) res+=query(ls,l,mid,x,y);
if(mid<y) res+=query(rs,mid+1,r,x,y);
return res;
}
void add(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
modify(1,1,n,dfn[top[x]],dfn[x],1);
x=fa[top[x]];
}
if(dfn[x]>dfn[y]) swap(x,y);
modify(1,1,n,dfn[x],dfn[y],1);
}
int ask(int x,int y){
int res=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
res=(res+query(1,1,n,dfn[top[x]],dfn[x]))%p;
x=fa[top[x]];
}
if(dfn[x]>dfn[y]) swap(x,y);
res=(res+query(1,1,n,dfn[x],dfn[y]))%p;
return res;
}
void print(){
for(int i=1;i<=n;i++){
cout << query(1,1,n,i,i) << ' ';
}cout << '\n';
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(int i=2;i<=n;i++){
int v;cin>>v;
v++;
vec[v].push_back(i);
}
dfs1(1,0);dfs2(1,1);
for(int i=1;i<=m;i++){
int l,r,z;
cin>>l>>r>>z;
r++,z++;
q[++cnt]=Q{i,l,z,0};
q[++cnt]=Q{i,r,z,1};
}
sort(q+1,q+1+cnt,cmp);
int tot=0;
for(int i=1;i<=cnt;i++){
while(tot<q[i].x){
add(1,++tot);
}
int id=q[i].id;
if(q[i].f){
ans[id].a1=ask(1,q[i].z);
}else{
ans[id].a2=ask(1,q[i].z);
}
}
for(int i=1;i<=m;i++){
cout << (ans[i].a1-ans[i].a2+p)%p <<'\n';
}
return 0;
}
旧词
看题目文艺才点进来,没想到是双倍经验。
问题变成了上面的题固定左端点,求 \(k\) 次幂。
我们考虑怎么维护 \(k\) 次幂。
当然就是,直接维护。
上一题的答案形式如下面的式子:\((dep_i)^1-(dep_i-1)^1\),现在它变成了 \((dep_i)^k-(dep_k-1)^k\),分别维护两个式子,然后直接做就行。
点击查看代码
#include<bits/stdc++.h>
#define ls u*2
#define rs u*2+1
#define ll long long
using namespace std;
const int N=5e4+10;
const int p=998244353;
ll qpow(ll a,ll b){
ll res=1;
while(b){
if(b&1) res=res*a%p;
a=a*a%p;
b>>=1;
}
return res%p;
}
int n,m,kk;
struct Q{
int id,x,y;
}q[N];
vector<int> vec[N];
bool cmp(Q a,Q b){
return a.x<b.x;
}
ll ans[N];
ll tr[N<<2],tag[N<<2],pre[N<<2];
int dfn[N],dfx,top[N],dep[N],fa[N],siz[N],hson[N],rnk[N];
void dfs1(int u,int f){
fa[u]=f;
siz[u]=1;
dep[u]=dep[f]+1;
for(int v:vec[u]){
if(v==f) continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[hson[u]]<siz[v]) hson[u]=v;
}
}
void dfs2(int u,int tp){
dfn[u]=++dfx;
rnk[dfx]=u;
top[u]=tp;
if(!hson[u]) return ;
dfs2(hson[u],tp);
for(int v:vec[u]){
if(v==fa[u] || v==hson[u]) continue;
dfs2(v,v);
}
}
void pushup(int u){
tr[u]=(tr[ls]+tr[rs])%p;
}
void upd(int u,int k){
tr[u]=(tr[u]+pre[u]*k%p)%p;
tag[u]=(tag[u]+k)%p;
}
void pushdown(int u){
if(!tag[u]) return ;
upd(ls,tag[u]);
upd(rs,tag[u]);
tag[u]=0;
}
void build(int u,int l,int r){
if(l==r){
pre[u]=(qpow(dep[rnk[l]],kk)-qpow(dep[rnk[l]]-1,kk)+p)%p;
return ;
}
int mid=(l+r)>>1;
build(ls,l,mid);build(rs,mid+1,r);
pre[u]=(pre[ls]+pre[rs])%p;
}
void modify(int u,int l,int r,int x,int y){
if(l>=x && r<=y){
tr[u]=(tr[u]+pre[u])%p;
tag[u]++;
return ;
}
pushdown(u);
int mid=(l+r)>>1;
if(x<=mid) modify(ls,l,mid,x,y);
if(mid<y) modify(rs,mid+1,r,x,y);
pushup(u);
}
ll query(int u,int l,int r,int x,int y){
if(l>=x && r<=y) return tr[u];
ll res=0;
int mid=(l+r)>>1;
pushdown(u);
if(x<=mid) res+=query(ls,l,mid,x,y);
if(mid<y) res+=query(rs,mid+1,r,x,y);
return res;
}
void add(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
modify(1,1,n,dfn[top[x]],dfn[x]);
x=fa[top[x]];
}
if(dfn[x]>dfn[y]) swap(x,y);
modify(1,1,n,dfn[x],dfn[y]);
}
ll ask(int x,int y){
ll res=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
res=(res+query(1,1,n,dfn[top[x]],dfn[x]))%p;
x=fa[top[x]];
}
if(dfn[x]>dfn[y]) swap(x,y);
res=(res+query(1,1,n,dfn[x],dfn[y]))%p;
return res;
}
void print(){
for(int i=1;i<=n;i++){
cout << query(1,1,n,i,i) << ' ';
}cout << '\n';
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m>>kk;
for(int i=2;i<=n;i++){
int v;cin>>v;
vec[v].push_back(i);
}
dfs1(1,0);dfs2(1,1);
build(1,1,n);
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
q[i]=Q{i,x,y};
}
sort(q+1,q+1+m,cmp);
int tot=1;
for(int i=1;i<=n;i++){
add(1,i);
while(tot<=m && q[tot].x==i){
ans[q[tot].id]=ask(1,q[tot].y);
tot++;
}
}
for(int i=1;i<=m;i++){
cout << ans[i]%p <<'\n';
}
return 0;
}

浙公网安备 33010602011771号