10.08联考题解

T1

值域 \(V \le 10^9\),我们是不太可能直接筛出所有数是否是质数的。但是询问区间长度之和 \(\sum r-l \le 5\times 10^7\),似乎可以筛这个区间内的所有数。

用埃筛先处理出 \(\sqrt r\) 以内的所有质数,然后用这些质数就可以对询问区间做埃筛,为了节省空间我们把下标转换成 \(x-l\),然后用已知的质数筛的话,起点从 \(> l\) 的第一个 \(p\) 的倍数开始,即 \(\lfloor \frac{l+p-1}{p}\rfloor \times p\),但是由于小于 \(p^2\) 的倍数一定已经被之前的质数筛过一遍了,所以起点是两者取 \(\max\)。

这题的特殊之处是,满足条件的除了质数以外,还有形如 \(4\times p\) 的形式,所以对每个区间做两个筛,筛 \(\lceil \frac{l}{4} \rceil\) 到 \(\lfloor \frac{r}{4}\rfloor\) 的区间内的质数情况,对于询问区间中 \(4\) 的倍数判断他除以 \(4\) 之后是不是质数即可。

时间复杂度 \(O(\sum(r-l)\log\log\sum(r-l))\)。

#include<bits/stdc++.h>
using namespace std;
#define _int __int128
#define ull unsigned long long
#define pii pair<int,int>
#define fst first
#define scd second
#define pq priority_queue
#define mkp make_pair
#define popcount(x) __builtin_popcount(x)
#define endl '\n'
int m,n;
const int N = 5e7+10;
bitset<N>vis,ok;
vector<int>prm;
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>m;
    int mx=32000;
    for(int i=2;i*i<=mx;i++){
        if(vis[i])continue;
        for(int j=i*i;j<=mx;j+=i){
            vis[j]=1;
        }
    }
    for(int i=2;i<=mx;i++){
        if(!vis[i]){
            prm.push_back(i);
        }
    }
    int lst=mx;
    for(int o=1;o<=m;o++){
        int ans=0,l,r;
        cin>>l>>r;
        for(int i=0;i<=lst;i++){
            vis[i]=ok[i]=0;
        }
        lst=r-l+1;
        int L = (l+3)/4;
        if(l==1)vis[0]=1;
        if(L==1)ok[0]=1;
        for(auto p:prm){
            if(p*p>r)break;
            for(int s=max(p*p,(l+p-1)/p*p);s<=r;s+=p){
                vis[s-l]=1;
            }
        }
        for(auto p:prm){
            if(p*p>r/4)break;
            for(int s=max(p*p,(L+p-1)/p*p);s<=r/4;s+=p){
                ok[s-L]=1;
            }
        }
        for(int i=l;i<=r;i++){
            if(!vis[i-l]||i==1||i==4||i==8||i%4==2||(i%4==0&&!ok[i/4-L])){
                ans++;
            }
        }
        cout<<ans<<endl;
    }
    return 0;
}

T2

给定一种哈希方式,和一个序列,询问字典序前 \(k\) 小的序列的哈希值。\(n,a_i \le 10^6,k \le 10^6\)。

\(k\) 的范围并不大,远小于 \(2^n-1\),所以可以考虑一下搜索,搜到前 \(k\) 个直接结束。先假设原序列没有相同的元素,我们要按照字典序生成的话显然是从值域的角度考虑。显然每次贪心地选当前最小的值,然后只能在下标更大的数里选下一个,没的选了就回溯。

当有相同元素时,一个位置的取值虽然相同却视作不同的子序列,所以我们要新增一个数组 \(pos\) 存储当前这一位所有的可能取自位置,我们下一位的可选位置至少要大于 \(\min pos_i\)。

考虑下一位取哪个值,直接枚举的话效率不高,所以考虑二分下一位的值是谁,用一个 ST 表记录一个值的最大出现位置,每次对当前值域查最大值,如果这个最大位置大于 \(\min pos_i\) 就是合法值。记一个 \(p_v\) 数组存储 \(v\) 这个数出现的下标位置,方便我们生成下一个位置的 \(pos\) 数组。

哈希值是可以带在 dfs 的参数里的,而且可以增量计算。

本质上是在子序列自动机上做搜索。

时间复杂度 \(O(n\log n+k(\log n+\log k))\)。

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define _int __int128
#define ull unsigned long long
#define pii pair<int,int>
#define fst first
#define scd second
#define pq priority_queue
#define mkp make_pair
#define popcount(x) __builtin_popcount(x)
#define endl '\n'
int n,k,b,m;
const int N = 1e6+10,inf=1e9;
int a[N],pb[N],s[N],c[N],ans[N];
int st[N][23];
vector<int>p[N];
inline void add(int &x,int y){
    x=(x+y>=m)?x+y-m:x+y;
}
int ask(int l,int r){
    int k=log2(r-l+1);
    return max(st[l][k],st[r-(1<<k)+1][k]);
}
int nxt(int now,int v){
    int l=v+1,r=N-10,res=N-9;
    while(l<=r){
        int mid=(l+r)/2;
        if(now<ask(v+1,mid)){
            res=mid;
            r=mid-1;
        }else{
            l=mid+1;
        }
    }
    return res;
}
void dfs(int hs,int now,vector<int> pos){
    int v=0;
    while(1){
        v=nxt(now,v);if(v==N-9)break;
        vector<int>nps(0);
        int nhs=(hs*b+v)%m;
        for(auto i=upper_bound(p[v].begin(),p[v].end(),now);i!=p[v].end();i++){
            for(auto j=pos.begin();j!=pos.end()&&(*j)<(*i);j++){
                if(k){
                   cout<<nhs<<endl;
                   nps.push_back(*i);
                   --k;
                }else{
                    exit(0);
                }
            }
        }
        dfs(nhs,*nps.begin(),nps);
    }
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    cin>>n>>k>>b>>m;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        p[a[i]].push_back(i);
        st[a[i]][0]=i;
    }
    for(int j=1;(1<<j)<=N-10;j++){
        for(int i=1;i+(1<<j)-1<=N-10;i++){
            st[i][j]=max(st[i][j-1],st[i+(1<<j-1)][j-1]);
        }
    }
    vector<int>pos(1,0);
    dfs(0,0,pos);
    return 0;
}

T3

第一步的发掘性质没看出来。

求出到根的异或和 \(val_x\),\(x\) 向祖先节点连边边权为 \(val_x \oplus val_f\),最小生成树有性质:图上两点之间的路径的最大边权的最小值是其 MST 上唯一路径的边权最大值。建出最小生成树后可以选择树上倍增求最大边权,时间复杂度 \(O(n^2\log n+q\log n)\)。

当前的问题在于连边过多,而一时间似乎想不出什么优化连边的方式。从异或的性质入手分析,如果一个节点向祖先连了最高位相同的两条边,边权记为 \(w_1,w_2\),则我们必然不会需要较大的那一条边 \(w_2\)。这是因为,\(w_1 \oplus w_2 < w_1<w_2\),而 \(w_1 \oplus w_2\) 是这两个祖先节点之间连边的边权,也就是说这三条边成环,而最小生成树必然不会包含环中最大的边,所以较大的边可以不连,所以对于每个最高位只要连边权最小的那一条边,总边数是 \(O(n\log V)\) 量级的。

现在唯一的难点是怎么找出每个点在每个最高位的最小边权。考虑 01 trie,在 dfs 的过程中把祖先的 \(val_x\) 插入 trie 中,钦定当前最高位是 \(k\),对于给定的值 \(x\) 求它与 trie 内的数的最小异或和(在最高位为 \(1\) 的前提下)。过程基本和一般的最小异或和一样,尽量走值相同的儿子节点,但是这题要在第 \(k\) 为强制走相反的儿子节点。离开子树后就要删除这个节点的信息。

值得注意的是,我们可以使用一种线段树式实现的 01 trie,也就是左节点 \(2p\),右节点 \(2p+1\),这棵树的好处是,其叶子节点 \(p\) 代表的数值为 \(p-2^B\),其中 \(B\) 为 trie 最高位。

时间复杂度 \(O(n\log^2 V+q\log n)\)。

#include<bits/stdc++.h>
using namespace std;
#define _int __int128
#define ull unsigned long long
#define pii pair<int,int>
#define fst first
#define scd second
#define pq priority_queue
#define mkp make_pair
#define popcount(x) __builtin_popcount(x)
#define endl '\n'
int n,m;
const int N = 5e4+10,V=1<<20;
int fa[N],w[N],dep[N],val[N],siz[N],bel[N];
int find(int x){
    return x==bel[x]?x:bel[x]=find(bel[x]);
}
vector<int>g[N];
struct node{int v,w;};
vector<node>e[N];
struct edge{
    int u,v,w;
};
int cnt[V<<2];
void ins(int p,int d,int x,int v){
    if(d==-1){
        cnt[p]+=v;
        return;
    }
    int b=(x>>d)&1;
    ins(p<<1|b,d-1,x,v);
    cnt[p]=cnt[p<<1]+cnt[p<<1|1];
}
int query(int p,int d,int x,int k){
    if(!cnt[p])return -1;
    if(d==-1)return p-(1<<20);
    if(cnt[p<<1|(x>>d&1)]&&d!=k)return query(p<<1|(x>>d&1),d-1,x,k);
    else return query(p<<1|!(x>>d&1),d-1,x,k);
}
vector<int>pos[V];
vector<edge>E;
int f[N][22],mx[N][22];
void dfs(int x){
    bel[x]=x;
    val[x]=val[fa[x]]^w[x];
    for(auto v:g[x]){
        dfs(v);
    }
}
void DFS(int x){
    for(int i=-1;i<=19;i++){
        int v=query(1,19,val[x],i);
        if(v!=-1&&(v^val[x])<(1<<i+1)){
            E.push_back({x,pos[v].back(),v^val[x]});
        }
    }
    pos[val[x]].push_back(x);
    ins(1,19,val[x],1);
    for(auto v:g[x])DFS(v);
    pos[val[x]].pop_back();
    ins(1,19,val[x],-1);
}
void dfs2(int x,int ft){
    dep[x]=dep[ft]+1;
    for(auto [v,w]:e[x]){
        if(v==ft)continue;
        f[v][0]=x;mx[v][0]=w;
        for(int i=1;i<=20;i++){
            f[v][i]=f[f[v][i-1]][i-1];
            mx[v][i]=max(mx[v][i-1],mx[f[v][i-1]][i-1]);
        }
        dfs2(v,x);
    }
}
int lca(int u,int v){
    int w=0;
    if(dep[u]<dep[v])swap(u,v);
    for(int i=20;i>=0;i--){
        int uu=f[u][i];
        if(dep[uu]>=dep[v]){
            w=max(w,mx[u][i]);
            u=uu;
        }
    }
    if(u==v)return w;
    for(int i=20;i>=0;i--){
        int uu=f[u][i],vv=f[v][i];
        if(uu!=vv){
            w=max({mx[u][i],mx[v][i],w});
            u=uu,v=vv;
        }
    }
    return max({w,mx[u][0],mx[v][0]});
}
bool cmp(edge a,edge b){
    return a.w<b.w;
}
void init(int nn,vector<int> _p,vector<int> _w){
    n=nn;
    for(int i=2;i<=n;i++){
        fa[i]=_p[i-1]+1;
        g[fa[i]].push_back(i);
        w[i]=_w[i-1];
    }
    dfs(1);
    DFS(1);
    sort(E.begin(),E.end(),cmp);
    int tot=0;
    for(auto [u,v,w]:E){
        if(find(u)!=find(v)){
            e[u].push_back({v,w});
            e[v].push_back({u,w});
            tot++;
            bel[find(u)]=find(v);
            if(tot==n-1)break;
        }
    }
    dfs2(1,0);
}
int minimum_energy(int u, int v){
    return lca(u+1,v+1);
}
signed main(){
    int n,m;
    cin>>n;
    vector<int>p(1,0),w(1,0);
    for(int i=1,x;i<n;i++){
        cin>>x;p.push_back(x);
    }
    for(int i=1,x;i<n;i++){
        cin>>x;w.push_back(x);
    }
    init(n,p,w);
    cin>>m;
    while(m--){
        int u,v;
        cin>>u>>v;
        cout<<minimum_energy(u,v)<<endl;
    }
}
posted @ 2026-10-09 08:09  farenara_sayowell  阅读(5)  评论(0)    收藏  举报