[jzoj 6086] [GDOI2019模拟2019.3.26] 动态半平面交 解题报告 (set+线段树)
题目链接:
https://jzoj.net/senior/#main/show/6086
题目:
题解:
- 一群数字的最小公倍数就是对它们质因数集合中的每个质因数的指数取$max$然后相乘
- 这样的子树查询一般都与$dfs$序有关
- 不妨把一个质因数$p$拆分成$p^1,p^2,p^3...$这样若干种颜色,每种颜色对答案的贡献都是$p$
- 我们从另一个角度来考虑如何处理“不同的数”。先不管深度,考虑两个点权相等的节点$u$和$v$,点权为$val$,他们自己的贡献是使得所有子树内包含他的节点答案乘以$val$,现在考虑重复了的贡献,同时包含$u$和$v$的节点答案被乘了两次,即包含$lca(u,v)$的节点答案需要被除去一个$val$。
- 多个节点的时候,只需按$dfs$序排序,相邻两个的$lca$处除即可。现在我们要考虑深度,只需要将询问离线并按深度排序,节点按深度排序后一个一个加入即可。维护点权相同的点可以用$set$,维护$dfs$序用线段树。这是一个很经典的做法
- 在线的话,把线段树可持久化即可
代码:
#include<algorithm> #include<cstring> #include<cstdio> #include<iostream> #include<cmath> #include<set> #include<map> using namespace std; typedef long long ll; const int N=1e5+15; const int M=1e7+15; const int mo=998244353; int n,tot; int a[N],head[N]; set <int> s[N*30]; map<int,int>hsh; struct EDGE { int to,nxt; }edge[N<<1]; inline int read() { char ch=getchar();int s=0,f=1; while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();} while (ch>='0'&&ch<='9') {s=(s<<3)+(s<<1)+ch-'0';ch=getchar();} return s*f; } int cnt; int prime[M],mn[M],vis[M]; void pre() { mn[1]=1; for (int i=2;i<M;i++) { if (!vis[i]) prime[++cnt]=i,mn[i]=i; for (int j=1;j<=cnt&&prime[j]*i<M;j++) { vis[prime[j]*i]=1;mn[prime[j]*i]=prime[j]; if (i%prime[j]==0) break; } } } int qpow(int a,int b) { int re=1; for (;b;b>>=1,a=1ll*a*a%mo) if (b&1) re=1ll*re*a%mo; return re; } void add(int u,int v) { edge[++tot]=(EDGE){v,head[u]}; head[u]=tot; } int tim; int fa[N][21],st[N],ed[N],dep[N],tid[N]; void dfs(int x,int p) { dep[x]=dep[p]+1; st[x]=++tim;tid[tim]=x; fa[x][0]=p; for (int i=1;i<=20;i++) fa[x][i]=fa[fa[x][i-1]][i-1]; for (int i=head[x];i;i=edge[i].nxt) { int y=edge[i].to; if (y==p) continue; dfs(y,x); } ed[x]=tim; } int lca(int x,int y) { if (dep[x]<dep[y]) swap(x,y); for (int i=20;i>=0;i--) if (dep[fa[x][i]]>=dep[y]) x=fa[x][i]; if (x==y) return x; for (int i=20;i>=0;i--) if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i]; return fa[x][0]; } bool cmp(const int &a,const int &b) {return dep[a]<dep[b];} int idx; int get(int x) { if (!hsh[x]) hsh[x]=++idx; return hsh[x]; } int sz; int rt[N],mul[N*600],ls[N*600],rs[N*600]; void ins(int &o,int lst,int l,int r,int x,int y) { o=++sz; mul[o]=1ll*mul[lst]*y%mo; ls[o]=ls[lst];rs[o]=rs[lst]; if (l==r) return; int mid=l+r>>1; if (x<=mid) ins(ls[o],ls[lst],l,mid,x,y); else ins(rs[o],rs[lst],mid+1,r,x,y); } int query(int o,int l,int r,int x,int y) { if (l>=x&&r<=y) return mul[o]; int mid=l+r>>1,re=1; if (x<=mid) re=1ll*re*query(ls[o],l,mid,x,y)%mo; if (y>mid) re=1ll*re*query(rs[o],mid+1,r,x,y)%mo; return re; } int b[N]; int main() { freopen("half.in","r",stdin); freopen("half.out","w",stdout); pre(); int k=read();n=read(); for (int i=1;i<=n;i++) a[i]=read(),b[i]=i; for (int i=1;i<n;i++) { int u=read(),v=read(); add(u,v);add(v,u); } dfs(1,0); mul[0]=1; sort(b+1,b+1+n,cmp); set<int>::iterator it1,it2,it3; for (int i=1;i<=n;i++) { int u=b[i],d=dep[u],tmp=a[u]; rt[d]=rt[dep[b[i-1]]]; while (tmp>1) { int t=mn[tmp],it=qpow(t,mo-2),z=1; while (tmp%t==0) { tmp/=t; z*=t; int y=get(z); s[y].insert(st[u]); it2=s[y].lower_bound(st[u]); it1=it2; if (it1!=s[y].begin()) --it1; it3=it2; ++it3; ins(rt[d],rt[d],1,n,st[u],t); int u1=0,u2=0; if (it2!=s[y].begin()) { u1=tid[*it1];int L=lca(u1,u); ins(rt[d],rt[d],1,n,st[L],it); } if (it3!=s[y].end()) { u2=tid[*it3];int L=lca(u2,u); ins(rt[d],rt[d],1,n,st[L],it); } if (u1&&u2) { int L=lca(u1,u2); ins(rt[d],rt[d],1,n,st[L],t); } } } } for (int i=dep[b[n]]+1;i<=n;i++) rt[i]=rt[i-1]; int q=read(),ans=0; while (q--) { int u=read()^(k*ans),d=min(n,dep[u]+read()^(k*ans)); printf("%d\n",query(rt[d],1,n,st[u],ed[u])); } return 0; }
星星之火,终将成燎原之势