題目
給定一棵樹。每個節點有一個權值a[i],詢問節點x和一個數字c,求c與x的子樹中與x深度值之差小於k的點的權值的最大公約數之積。
題解
爆0代碼欣賞
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define N 100010
#define M 10000010
#define N1 670010
#define mo 998244353
#define LL long long
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
struct note{
int to,next;
}edge[N<<1];
struct noq{
int x,w;
}que[N];
struct no1{
int p,e,x,xs,id;
}q1[N*60];
LL tr1[33],tr2[33];
int tot,head[N];
int pri[N1],f[M],c[M];
int fa[N],dep[N],qu[N],bfn[N],siz[N];
bool bz[M];
int n,q,k,Ans,cs,ans[N][31];
int u,v,x,w,las,wz;
int i,j,l,m,CNT,TOT,t2,t3,a1,a2;
int zl[N*30],cx;
bool p30;
void lb(int x,int y){
edge[++tot].to=y;
edge[tot].next=head[x];
head[x]=tot;
}
int lowbit(int x){return x&(-x);}
void add(int x,LL z){
for(;x<=30;x=x+lowbit(x))tr1[x]=tr1[x]+z;
}
void add0(int x,LL z){
for(;x<=30;x=x+lowbit(x))tr2[x]=tr2[x]+z;
}
LL qry(int x){
LL rs=0;
for(;x;x=x-lowbit(x))rs=rs+tr1[x];
return rs;
}
LL qry0(int x){
LL rs=0;
for(;x;x=x-lowbit(x))rs=rs+tr2[x];
return rs;
}
int ksm(int x,int y){
int rs=1;
for(;y;y>>=1,x=(1ll*x*x)%mo)
if(y&1)rs=(1ll*rs*x)%mo;
return rs;
}
void pre(){
int i,j;
fo(i,2,M-10){
if(!bz[i]){
pri[++CNT]=i;
f[i]=i;
}
for(j=1;j<=CNT&&i*pri[j]<=M-10;j++){
bz[i*pri[j]]=1;
f[i*pri[j]]=pri[j];
if(i%pri[j]==0)break;
}
}
}
void bfs(int x){
int ql=0,qr=1,i;
qu[1]=x;
while(ql<qr){
x=qu[++ql];
bfn[x]=ql;
siz[x]=1;
for(i=head[x];i;i=edge[i].next)
if(fa[x]^edge[i].to){
fa[edge[i].to]=x;
dep[edge[i].to]=dep[x]+1;
qu[++qr]=edge[i].to;
}
}
}
int gcd(int x,int y){return !y?x:gcd(y,x%y);}
bool cmp(no1 a,no1 b){return a.p<b.p||(a.p==b.p&&a.x<b.x);}
int main(){
scanf("%d%d%d",&n,&q,&k);
fo(i,1,n)scanf("%d",&c[i]);
fo(i,1,n-1){
scanf("%d%d",&u,&v);
lb(u,v);lb(v,u);
}
bfs(1);
fd(i,n,1){
siz[fa[qu[i]]]+=siz[qu[i]];
}
pre();
if(n<=1000){
fo(cs,1,q){
scanf("%d%d",&x,&w);
l=bfn[x];
Ans=1;
while(l<=n){
if(dep[qu[l]]-dep[x]>=k||(dep[qu[l]]<=dep[x]&&(qu[l]^x)))break;
Ans=(1ll*Ans*gcd(w,c[qu[l]]))%mo;
l++;
}
printf("%d\n",Ans);
}
return 0;
}
fo(i,1,q){
scanf("%d%d",&que[i].x,&que[i].w);
u=que[i].w;
while(u^1){
zl[++t2]=f[u];
u=u/f[u];
}
}
sort(zl+1,zl+t2+1);
cx=unique(zl+1,zl+t2+1)-zl-1;
if(cx<=30){
TOT=0;
fo(i,1,n){
u=c[i];
las=0;
while(u^1){
if(f[u]^las){
q1[++TOT].p=f[u];
q1[TOT].e=1;
q1[TOT].xs=2;
q1[TOT].x=bfn[i];
}else q1[TOT].e++;
las=f[u];
u=u/f[u];
}
}
fo(i,1,q){
u=que[i].w;
if(u==1)continue;
las=0;
a1=TOT+1;
while(u^1){
if(f[u]^las){
q1[++TOT].p=f[u];
q1[TOT].e=1;
q1[TOT].x=bfn[que[i].x]-1;
q1[TOT].xs=-1;
q1[TOT].id=i;
}else q1[TOT].e++;
las=f[u];
u=u/f[u];
}
a2=TOT;
int L1=bfn[que[i].x],R1=L1+siz[que[i].x]-1,Mid;
while(L1<=R1){
Mid=(L1+R1)>>1;
if(dep[qu[Mid]]-dep[que[i].x]<k)w=Mid,L1=Mid+1;else R1=Mid-1;
}
fo(j,a1,a2){
q1[++TOT]=q1[j];
q1[TOT].x=w;
q1[TOT].xs=1;
}
}
sort(q1+1,q1+TOT+1,cmp);
int wz1=1,wl,wr;
wz=1;
fo(i,1,cx){
while(wz<=TOT&&q1[wz].p<zl[i])wz++;
wl=-1;
while(wz<=TOT&&q1[wz].p==zl[i]){
if(wl==-1)wl=wz;wr=wz;
if(q1[wz].xs==2){
add(q1[wz].e,q1[wz].e);
add0(q1[wz].e,1);
}else{
w=qry0(30)-qry0(q1[wz].e-1);
ans[q1[wz].id][i]+=1ll*w*q1[wz].xs*q1[wz].e;
w=qry(q1[wz].e-1);
ans[q1[wz].id][i]+=1ll*w*q1[wz].xs;
}
wz++;
}
fo(j,wl,wr)if(q1[j].xs==2){
add(q1[j].e,-q1[j].e);
add0(q1[j].e,-1);
}
}
fo(i,1,q){
Ans=1;
fo(j,1,cx){
w=ksm(zl[j],ans[i][j]);
Ans=(1ll*Ans*w)%mo;
}
printf("%d\n",Ans);
}
return 0;
}
fo(cs,1,q){
l=bfn[x];
Ans=1;
while(l<=n){
if(dep[qu[l]]-dep[x]>=k||(dep[qu[l]]<=dep[x]&&(qu[l]^x)))break;
Ans=(1ll*Ans*gcd(w,c[qu[l]]))%mo;
l++;
}
printf("%d\n",Ans);
}
return 0;
}
Para. 1
A這題的過程十分坎坷。
比賽的時候差一點點AC了。就只有2個地方想錯了,其他地方和題解基本一樣。
錯誤的地方:①查詢x的子樹中與x深度值之差小於k的點,這些點並不一定對應着bfs序中的一段TOT。。。。
②質數雖然很多,但是總操作數不會超過,所以按照比賽的時候的打法,然後改一下就可以AC了。
Para. 2
苦逼的我選擇重新打一遍。。。。
這道題目就幾個點。
將詢問和修改一起搞。修正錯誤②,通過排序,固定住一個質數。指數之間互不影響。
考慮在dfs序上做文章,那麼修改有兩個,在x打個+1標記,在x的k祖先打一個-1標記。
然後轉化成二維數點問題了。
如何離線做?離線,也就是轉化成差分問題。每個詢問拆成2個,一個是-的,一個是+的。
Para. 3
涉及了幾個小細節,發現我也進步了好多啊。
查詢區間裏.
類比JZOJ 5919,求中的一段和大於等於數a的個數。
固定住一個質數後,再按dfs序從小到大排序。然後用兩個樹狀數組求出這個質數對答案的貢獻。兩樹狀數組——分類求值。
最後快速冪一下即可。
Para. 4
本人輸就輸在沒有好好計算時間複雜度。
總共個操作點,排序,樹狀數組,快速冪一個。
所以總時間複雜度不會超過。
Para. 5
調試調了很多個小時。無論怎麼集中精力,在碼題的時候都會有地方寫錯。
錯誤之處:AC代碼中的寫成了。
有9個點WA了。
處理措施1:拍小數據。小數據拍過了,拍隨機的大數據。但是無任何進展。
處理措施2(比較新穎的調試方式,在陷入很深的困境的時候使用)。
前提:對程序的整個過程十分熟悉。
答案中,質因子2的個數錯了,所以只看2。
答案的計算只涉及到整個dfs序中的某一小部分,然後分別將正確和錯誤的程序中,每個點對答案的貢獻寫出來。如下面兩張圖。
如果遍歷點的順序不一樣,還需要特意地排個序。
每個打了調試標記的地方都去設個斷點。
在這個時候調試終於有了進展。
發現第144行進不去,找到錯誤了。
大功告成!
Para. n
虛樹也是可以搞的。
如果不能用普通的調試方法調出程序,那麼嘗試一些腦洞打開的方法。
前提:對程序的整個過程十分熟悉!!!
AC代碼欣賞
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 100010
#define N1 2000010
#define M 10000010
#define LL long long
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define mo 998244353
using namespace std;
struct note{
int to,next;
}edge[N<<1];
int tot,head[N];
struct noc{
int p,e,x,tp,id;
}cz[N1];
int i,j,k,l,n,m,q,las,TOT,CNT;
int u,v,wl,wr,w,ans[N];
int pri[670010],f[M],fa[17][N];
int fk[N],T,dfn[N],tar[N],siz[N];
int tr[33],tr1[33],cc[N];
bool bz[M];
int ksm(int x,long long y){
y=y%(mo-1);
int rs=1;
for(;y;y>>=1,x=(1ll*x*x)%mo)if(y&1)rs=(1ll*rs*x)%mo;
return rs;
}
void pre(){
int i,j;
fo(i,2,M-10){
if(!bz[i]){
pri[++TOT]=i;
f[i]=i;
}
for(j=1;j<=TOT&&i*pri[j]<=M-10;j++){
bz[i*pri[j]]=1;
f[i*pri[j]]=pri[j];
if(i%pri[j]==0)break;
}
}
}
void lb(int x,int y){
edge[++tot].to=y;
edge[tot].next=head[x];
head[x]=tot;
}
void dg(int x){
int i;
dfn[x]=++T;tar[T]=x;
siz[x]=1;
for(i=head[x];i;i=edge[i].next)
if(edge[i].to^fa[0][x]){
fa[0][edge[i].to]=x;
dg(edge[i].to);
siz[x]=siz[x]+siz[edge[i].to];
}
}
bool cmp(noc a,noc b){
return a.p<b.p||(a.p==b.p&&a.x<b.x)||(a.p==b.p&&a.x==b.x&&a.tp<b.tp);
}
int lowbit(int x){return x&(-x);}
void add(int x,int num){
for(;x<=30;x=x+lowbit(x))tr[x]=tr[x]+num;
}
int qry(int x){
int rs=0;
for(;x;x=x-lowbit(x))rs=rs+tr[x];
return rs;
}
void add0(int x,int num){
for(;x<=30;x=x+lowbit(x))tr1[x]=tr1[x]+num;
}
int qry0(int x){
int rs=0;
for(;x;x=x-lowbit(x))rs=rs+tr1[x];
return rs;
}
int main(){
scanf("%d%d%d",&n,&q,&k);
pre();
fo(i,1,n)scanf("%d",&cc[i]);
fo(i,1,n-1){
scanf("%d%d",&u,&v);
lb(u,v);lb(v,u);
}
dg(1);
fo(j,1,16)fo(i,1,n)fa[j][i]=fa[j-1][fa[j-1][i]];
fo(i,1,n){
u=i;
fd(j,16,0)if((1<<j)&k)u=fa[j][u];
fk[i]=u;
}
fo(i,1,n){
m=cc[i];
if(m==1)continue;
wl=CNT+1;
las=0;
while(m^1){
if(f[m]^las){
cz[++CNT].p=f[m];
cz[CNT].e=1;
cz[CNT].x=dfn[i];
cz[CNT].tp=0;
}else cz[CNT].e++;
las=f[m];
m=m/f[m];
}
wr=CNT;
fo(j,wl,wr)if(fk[tar[cz[j].x]]){
cz[++CNT].p=cz[j].p;
cz[CNT].e=cz[j].e;
cz[CNT].x=dfn[fk[tar[cz[j].x]]];
cz[CNT].tp=-1;
}
}
fo(i,1,q){
ans[i]=1;
scanf("%d%d",&u,&m);
if(m==1)continue;
wl=CNT+1;
las=0;
while(m^1){
if(f[m]^las){
cz[++CNT].p=f[m];
cz[CNT].e=1;
cz[CNT].x=u;
cz[CNT].tp=1;
cz[CNT].id=i;
}else cz[CNT].e++;
las=f[m];
m=m/f[m];
}
wr=CNT;
fo(j,wl,wr){
cz[++CNT].p=cz[j].p;
cz[CNT].e=cz[j].e;
cz[CNT].x=dfn[cz[j].x]+siz[cz[j].x]-1;
cz[CNT].tp=2;
cz[CNT].id=i;
cz[j].x=dfn[cz[j].x]-1;
}
}
sort(cz+1,cz+CNT+1,cmp);
i=1;
for(;i<=CNT;i++){
wl=-1;
while(i<=CNT){
if(wl==-1)wl=i;
wr=i;
if(cz[i].tp<1){
if(cz[i].tp==0){
add(cz[i].e,cz[i].e);
add0(cz[i].e,1);
}else{
add(cz[i].e,-cz[i].e);
add0(cz[i].e,-1);
}
}else{
if(cz[i].tp==1&&cz[i].x==0){
i++;
continue;
}
w=qry(cz[i].e);
w=w+(qry0(30)-qry0(cz[i].e))*cz[i].e;
if(cz[i].tp==1)w=-w;
if(w>0){
ans[cz[i].id]=(1ll*ans[cz[i].id]*ksm(cz[i].p,w))%mo;
}else{
ans[cz[i].id]=(1ll*ans[cz[i].id]*ksm(cz[i].p,1ll*-w*(mo-2)))%mo;
}
}
if(cz[i+1].p==cz[i].p)i++;
else break;
}
wr=i;
fo(j,wl,wr){
if(cz[j].tp==-1){
add(cz[j].e,cz[j].e);
add0(cz[j].e,1);
}
if(cz[j].tp==0){
add(cz[j].e,-cz[j].e);
add0(cz[j].e,-1);
}
}
}
fo(i,1,q)printf("%d\n",ans[i]);
return 0;
}