10.08联考题解
值域 \(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;
}
给定一种哈希方式,和一个序列,询问字典序前 \(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;
}
第一步的发掘性质没看出来。
求出到根的异或和 \(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;
}
}

浙公网安备 33010602011771号