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 的空间。

  1. 值域分块数组可以用 short。
  2. 块长设大一点。
  3. 使用版本树,别写在线!
点击查看代码
#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;
}
posted @ 2025-05-17 09:11  Tighnari  阅读(21)  评论(0)    收藏  举报