P4119 [Ynoi2018] 未来日记

文章介绍了如何使用值域分块和并查集优化数据结构,解决k值查询的问题,涉及整块处理、散块计数、差分修改等技术,以提高时间效率。
摘要由CSDN通过智能技术生成

它来了!

分析一下第一个操作,不是写过嘛,并查集

分析一下第二个操作,二分套二分答案

拿下了这题

仔细分析,貌似时间复杂度是错的O(sqrt(n)*nlognlogv)

我们考虑块套块 时间复杂度O(n*sqrt(n))

对1e5的值域进行分块

求k值我们可以先找是第几个块,再找到是块内第几个数

如何做?

散块

我们可以做一个桶来临时存数量

整块

我们可以做一个前缀和,O(1)询问范围内的数量与k的关系,小于k我们就累加,否则我们就找到了那个块,找到第几个数也是一样的原理

因此我们维护bc[i][j] i个块内 j个值域块内的数量,c[i][j] i个块内j个数值的数量,那么散块也需要临时维护t1[i]第i个值域块的数量,t2[i]第i个数值的数量,提前预处理

如果只有一个散块可以直接找nth_element

inline int kth(int l,int r,int k){
    int p=pos[l];
    int q=pos[r];
    ll res=0;
    ll sum=0;
    if(p==q){
        pushdown(p);
        for(int i=l;i<=r;i++){
            t2[i]=a[i];
        }
        nth_element(t2+l,t2+l+k-1,t2+r+1);
        res=t2[l+k-1];//0->k
        for(int i=l;i<=r;i++){
            t2[i]=0;//清空
        }
    }else{
        //散块计数
        pushdown(p);
        for(int i=l;i<=R[p];i++){
            t1[PP[a[i]]]++;
            t2[a[i]]++;
        }
        pushdown(q);
        for(int i=L[q];i<=r;i++){
            t1[PP[a[i]]]++;
            t2[a[i]]++;
        }
        //整块处理
        for(int i=1;i<=tt;i++){//枚举值域块
            if(sum+t1[i]+bc[q-1][i]-bc[p][i]>=k){//[p+1,q-1]
                for(int j=LL[i];j<=RR[i];j++){//枚举块内值域
                    if(sum+t2[j]+c[q-1][j]-c[p][j]>=k){//找到位置
                        //查询结束前清空
                        for(int o=l;o<=R[p];o++){//[l,R[p]]
                            t1[PP[a[o]]]--;
                            t2[a[o]]--;
                        }
                        for(int o=L[q];o<=r;o++){//[R[q],r]
                            t1[PP[a[o]]]--;
                            t2[a[o]]--;
                        }
                        //返回答案
                        return j;//kth
                    }else{
                        sum+=(t2[j]+c[q-1][j]-c[p][j]);
                    }
                }
            }else{
                sum+=(t1[i]+bc[q-1][i]-bc[p][i]);//累加
            }
        }
    }
    return res;
}

结合操作一

我们维护val[i][j] 记录在第i块内的第j个位置的值,index[i][j]记录在第i个块内的a[j]在什么位置

loc[i]记录index[i][a[i]]

预处理

inline void build(int id){//建立关系
    int cur=0;//根标号
    for(int i=1;i<=blo;i++){//清空关系
        index[id][val[id][i]]=0;
    }
    for(int i=L[id];i<=R[id];i++){
        if(!index[id][a[i]]){
            cur++;
            index[id][a[i]]=cur;
            val[id][cur]=a[i];
        }
        loc[i]=index[id][a[i]];
    }
}

这样我们可以通过3个数组推出a[i]的真实值

inline void pushdown(int id){//还原数组
    for(int i=L[id];i<=R[id];i++){
        a[i]=val[id][loc[i]];//
    }
}

合并操作

inline void merge(int id,int x,int y){//合并
    index[id][y]=index[id][x];
    val[id][index[id][x]]=y;
    index[id][x]=0;
}

 修改我们考虑差分,再单块处理后,前缀和,时间复杂度不变

散块

直接modify

整块

如果没有x,直接跳过

如果没有x,有y  转移贡献

如果有x,有y  +合并

inline void modify(int l,int r,int x,int y){//散块
    for(int i=l;i<=r;i++){
        if(a[i]==x){
            bc[pos[i]][PP[x]]--;
            bc[pos[i]][PP[y]]++;
            c[pos[i]][x]--;
            c[pos[i]][y]++;
            a[i]=y;
        }
    }
}
inline void update(int l,int r,int x,int y){//处理每个块的数据可以差分处理
    if((x==y) || c[pos[r]][x]-c[pos[l]-1][x]==0 ){//x==y 或者是 区间内没有x
        return;
    }
    //差分单独处理这块的答案
    for(int i=t;i>=pos[l];i--){
        bc[i][PP[x]]-=bc[i-1][PP[x]];
        bc[i][PP[y]]-=bc[i-1][PP[y]];
        c[i][x]-=c[i-1][x];
        c[i][y]-=c[i-1][y];
    }
    int p=pos[l];
    int q=pos[r];
    if(p==q){
        pushdown(p);
        modify(l,r,x,y);//散块
        build(p);
        for(int i=p;i<=t;i++){
            bc[i][PP[x]]+=bc[i-1][PP[x]];
            bc[i][PP[y]]+=bc[i-1][PP[y]];
            c[i][x]+=c[i-1][x];
            c[i][y]+=c[i-1][y]; 
        }
    }else{
        pushdown(p);
        modify(l,R[p],x,y);//散块
        build(p);
        pushdown(q);
        modify(L[q],r,x,y);//散块
        build(q);
        for(int i=p+1;i<=q-1;i++){
            if(!c[i][x]){//不存在x 
                continue;
            }else if(c[i][y]){
                pushdown(i);
                modify(L[i],R[i],x,y);
                build(i);	
            }else{
                bc[i][PP[y]]+=c[i][x];
                bc[i][PP[x]]-=c[i][x];
                c[i][y]+=c[i][x];
                c[i][x]=0;
                merge(i,x,y);
            }
        }
        for(int i=p;i<=t;i++){
            bc[i][PP[x]]+=bc[i-1][PP[x]];
            bc[i][PP[y]]+=bc[i-1][PP[y]];
            c[i][x]+=c[i-1][x];
            c[i][y]+=c[i-1][y]; 
        }
    }
}

完整代码

#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#define INF (1ll<<60)
using namespace std;
typedef long long ll;
namespace Lan {
    inline string sread() {
        string s=" ";char e=getchar();
        while(e==' '||e=='\n')e=getchar();
        while(e!=' '&&e!='\n')s+=e,e=getchar();
        return s;
    }
    inline void swrite(string s){
        for(char e:s)putchar(e);
        printf("\n");
    }
    inline ll read() {
        ll x=0,y=1;char c=getchar();
        while(!isdigit(c)){if(c=='-')y=-1;c=getchar();}
        while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
        return x*=y;
    }
    inline void write(ll x) {
        if(x<0){x=-x,putchar('-');}ll sta[35],top=0;
        do sta[top++]=x%10,x/=10;while(x);
        while(top)putchar(sta[--top]+'0');
    }
}using namespace Lan;
const int N=1e5+9;
const int M=1e5+2;
const int B=5e2+9;
int a[N],bc[B][N],c[B][N];//块内的值域块中的数的数量,以及该数值的数
int L[B],R[B],pos[N];
int LL[B],RR[B],PP[N];
int index[B][N],val[B][N],loc[N];
int t1[B],t2[N];//散块查kth
int blo,t,tt;
inline void merge(int id,int x,int y){//合并
    index[id][y]=index[id][x];
    val[id][index[id][x]]=y;
    index[id][x]=0;
}
inline void pushdown(int id){//还原数组
    for(int i=L[id];i<=R[id];i++){
        a[i]=val[id][loc[i]];//
    }
}
inline void build(int id){//建立关系
    int cur=0;//根标号
    for(int i=1;i<=blo;i++){//清空关系
        index[id][val[id][i]]=0;
    }
    for(int i=L[id];i<=R[id];i++){
        if(!index[id][a[i]]){
            cur++;
            index[id][a[i]]=cur;
            val[id][cur]=a[i];
        }
        loc[i]=index[id][a[i]];
    }
}
inline void modify(int l,int r,int x,int y){//散块
    for(int i=l;i<=r;i++){
        if(a[i]==x){
            bc[pos[i]][PP[x]]--;
            bc[pos[i]][PP[y]]++;
            c[pos[i]][x]--;
            c[pos[i]][y]++;
            a[i]=y;
        }
    }
}
inline void update(int l,int r,int x,int y){//处理每个块的数据可以差分处理
    if((x==y) || c[pos[r]][x]-c[pos[l]-1][x]==0 ){//x==y 或者是 区间内没有x
        return;
    }
    //差分单独处理这块的答案
    for(int i=t;i>=pos[l];i--){
        bc[i][PP[x]]-=bc[i-1][PP[x]];
        bc[i][PP[y]]-=bc[i-1][PP[y]];
        c[i][x]-=c[i-1][x];
        c[i][y]-=c[i-1][y];
    }
    int p=pos[l];
    int q=pos[r];
    if(p==q){
        pushdown(p);
        modify(l,r,x,y);//散块
        build(p);
        for(int i=p;i<=t;i++){
            bc[i][PP[x]]+=bc[i-1][PP[x]];
            bc[i][PP[y]]+=bc[i-1][PP[y]];
            c[i][x]+=c[i-1][x];
            c[i][y]+=c[i-1][y]; 
        }
    }else{
        pushdown(p);
        modify(l,R[p],x,y);//散块
        build(p);
        pushdown(q);
        modify(L[q],r,x,y);//散块
        build(q);
        for(int i=p+1;i<=q-1;i++){
            if(!c[i][x]){//不存在x 
                continue;
            }else if(c[i][y]){
                pushdown(i);
                modify(L[i],R[i],x,y);
                build(i);	
            }else{
                bc[i][PP[y]]+=c[i][x];
                bc[i][PP[x]]-=c[i][x];
                c[i][y]+=c[i][x];
                c[i][x]=0;
                merge(i,x,y);
            }
        }
        for(int i=p;i<=t;i++){
            bc[i][PP[x]]+=bc[i-1][PP[x]];
            bc[i][PP[y]]+=bc[i-1][PP[y]];
            c[i][x]+=c[i-1][x];
            c[i][y]+=c[i-1][y]; 
        }
    }
}
inline int kth(int l,int r,int k){
    int p=pos[l];
    int q=pos[r];
    ll res=0;
    ll sum=0;
    if(p==q){
        pushdown(p);
        for(int i=l;i<=r;i++){
            t2[i]=a[i];
        }
        nth_element(t2+l,t2+l+k-1,t2+r+1);
        res=t2[l+k-1];//0->k
        for(int i=l;i<=r;i++){
            t2[i]=0;//清空
        }
    }else{
        //散块计数
        pushdown(p);
        for(int i=l;i<=R[p];i++){
            t1[PP[a[i]]]++;
            t2[a[i]]++;
        }
        pushdown(q);
        for(int i=L[q];i<=r;i++){
            t1[PP[a[i]]]++;
            t2[a[i]]++;
        }
        //整块处理
        for(int i=1;i<=tt;i++){//枚举值域块
            if(sum+t1[i]+bc[q-1][i]-bc[p][i]>=k){//[p+1,q-1]
                for(int j=LL[i];j<=RR[i];j++){//枚举块内值域
                    if(sum+t2[j]+c[q-1][j]-c[p][j]>=k){//找到位置
                        //查询结束前清空
                        for(int o=l;o<=R[p];o++){//[l,R[p]]
                            t1[PP[a[o]]]--;
                            t2[a[o]]--;
                        }
                        for(int o=L[q];o<=r;o++){//[R[q],r]
                            t1[PP[a[o]]]--;
                            t2[a[o]]--;
                        }
                        //返回答案
                        return j;//kth
                    }else{
                        sum+=(t2[j]+c[q-1][j]-c[p][j]);
                    }
                }
            }else{
                sum+=(t1[i]+bc[q-1][i]-bc[p][i]);//累加
            }
        }
    }
    return res;
}
int main(){
    // ios::sync_with_stdio(false);
    // cin.tie(0),cout.tie(0);
    int n,m;
    n=read(),m=read();
    for(int i=1;i<=n;i++){
        a[i]=read();
    }
    //分块
    blo=sqrt(n);
    t=sqrt(n);
    for(int i=1;i<=t;i++){
        L[i]=(i-1)*t+1;
        R[i]=i*t;
    }
    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;
        }
    }
    //值域分块
    tt=sqrt(M);
    for(int i=1;i<=tt;i++){
        LL[i]=(i-1)*tt+1;
        RR[i]=i*tt;
    }
    if(RR[tt]<M){
        tt++;
        LL[tt]=RR[tt-1]+1;
        RR[tt]=M;
    }
    for(int i=1;i<=tt;i++){
        for(int j=LL[i];j<=RR[i];j++){
            PP[j]=i;
        }
    }
    //预处理
    //值域分块
    for(int i=1;i<=t;i++){
        for(int j=1;j<=t+100;j++){
            bc[i][j]=bc[i-1][j];//块继承
        }
        for(int j=1;j<M;j++){
            c[i][j]=c[i-1][j];//数量继承
        }
        for(int j=L[i];j<=R[i];j++){
            bc[i][PP[a[j]]]++;//继续记录
            c[i][a[j]]++;
        }
    }
    //并查集
    for(int i=1;i<=t;i++){
        build(i);
    }
    for(int i=1;i<=m;i++){
        int op;
        op=read();
        int l,r;
        l=read(),r=read();
        if(op==1){
            int x,y;
            x=read(),y=read();
            update(l,r,x,y);
        }else{
            int k;
            k=read();
            cout<<kth(l,r,k)<<'\n';
        }
    }
    return 0;
}

学会了值域分块求k值,学完第二分块并查集,感觉第一分块的操作一并查集那块挺好写的

差分修改还是很妙的

下一个大分块 maybe 天降之物?

  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
TokenType.SYMBOL_LEFT_PAREN) { match(TokenType.SYMBOL_LEFT_PAREN); expression(); match(TokenType.SYMBOL_RIGHT_PAREN); } else { throw new Exception("Syntax error"); } } private void match(TokenType type) throws Exception { if (tokenList.get(index).getType() == type) { index++; } else { throw new Exception("Syntax error"); } } private int getVariableAddress(String identifier) throws Exception { if (!symbolTable.containsKey(identifier)) { 这是一道程序设计竞赛题目,需要设计算法来解决。题目描述为: 有一个大小为 $ throw new Exception("Undefined variable"); } return symbolTable.get(identifier); } } ``` 在语义分析程序n\times m$ 的网格图,每个格子中有一个整数。定义一个点 $(i,j)$ 的权中,我们首先定义了四个成员变量:词法单元列表、当前处理的词法单元下标、符号表和四元式列表。其中符号表用于存储变量名和对应的内存地址,值为 $a_{i,j}$ 与其上下左右四个点权值的和的乘积,即 $a四元式列表用于存储生成的四元式。 然后,我们定义了一系列方法来进行语义分_{i,j}\times (a_{i-1,j}+a_{i+1,j}+a_{i,j-1}析和生成四元式。这些方法分别对应PL/0语言的不同语法结构,例如程序+a_{i,j+1})$。现在你需要找到一个点 $(i,j)$,使得其权值最、块、语句、表达式、项和因子。在处理每个语法结构时,我们先判断当前大,输出这个最大权值。 需要用到搜索算法或动态规划算法来解决这个问题,具体实现可以参考题目解析。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值