luogu P3899 [湖南集訓]談笑風生
阿新 • • 發佈:2018-12-06
nmyzd,mgdhls,bnmbzdgdnlql,a,wgttxfs
對於一個點\(a\),點\(b\)只有可能是他的祖先或者在\(a\)子樹裡
如果點\(b\)是\(a\)祖先,那麼答案為a子樹大小\(sz_a-1\)
否則,答案為\(sz_b-1\)
加上\(k\)的限制後,如果根節點1的深度\(de_1=1\)那麼節點\(a\)的答案就是\(\sum_{b在a子樹中,b\ne a,dist(a,b)\leq k}\ \ (sz_b-1)+\min(k,de_a-1)*(sz_a-1)\).後半段可以直接算,前半段的話,對每個節點開個線段樹,儲存子樹中深度為某個值的\(\sum sz_a-1\)
// luogu-judger-enable-o2 #include<bits/stdc++.h> #define LL long long #define il inline #define re register #define db double #define eps (1e-7) using namespace std; const int N=300000+10; const LL inf=(1ll<<50); il LL rd() { re LL x=0,w=1;re char ch=0; while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();} while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();} return x*w; } int to[N<<1],nt[N<<1],hd[N],tot=1; il void add(int x,int y) { ++tot,to[tot]=y,nt[tot]=hd[x],hd[x]=tot; ++tot,to[tot]=x,nt[tot]=hd[y],hd[y]=tot; } LL s[N*30]; int ch[N*30][2],tt; #define mid ((l+r)>>1) il void psup(int o){s[o]=s[ch[o][0]]+s[ch[o][1]];} void bui(int o,int l,int r,int lx,LL x) { if(l==r){s[o]=x;return;} if(lx<=mid) bui(ch[o][0]=++tt,l,mid,lx,x); else bui(ch[o][1]=++tt,mid+1,r,lx,x); psup(o); } int merge(int o1,int o2) { if(!o1) return o2; if(!o2||o1==o2) return o1; int o3=++tt; s[o3]=s[o1]+s[o2]; ch[o3][0]=merge(ch[o1][0],ch[o2][0]); ch[o3][1]=merge(ch[o1][1],ch[o2][1]); return o3; } LL quer(int o,int l,int r,int ll,int rr) { if(!o) return 0; if(ll<=l&&r<=rr) return s[o]; LL an=0; if(ll<=mid) an+=quer(ch[o][0],l,mid,ll,rr); if(rr>mid) an+=quer(ch[o][1],mid+1,r,ll,rr); return an; } int n,m,rt[N]; int sz[N],de[N]; void dfs(int x,int ffa) { sz[x]=1; for(int i=hd[x];i;i=nt[i]) { int y=to[i]; if(y==ffa) continue; de[y]=de[x]+1; dfs(y,x); sz[x]+=sz[y]; } if(sz[x]>1) { bui(rt[x]=++tt,1,n,de[x],sz[x]-1); for(int i=hd[x];i;i=nt[i]) { int y=to[i]; if(y==ffa) continue; rt[x]=merge(rt[x],rt[y]); } } } int main() { n=rd(),m=rd(); for(int i=1;i<n;i++) add(rd(),rd()); de[1]=1; dfs(1,0); while(m--) { int p=rd(),k=rd(); printf("%lld\n",quer(rt[p],1,n,de[p]+1,de[p]+k)+1ll*(sz[p]-1)*min(de[p]-1,k)); } return 0; }
upd 12.6:
其實這題可以也可以用長鏈剖分,因為我們可以設\(f_{x,j}\)為點\(x\)子樹內深度為\(de_x+j\)的所有點的\((sz_x-1)\)之和,轉移是\(f_{x,j}=\sum_{y=son_x}f_{y,j-1}\),這個可以長鏈剖分後優化,直接繼承重兒子狀態,暴力轉移輕兒子狀態
處理詢問和上面類似,只不過這裡要把\(f_{x,j}\)搞成字尾和形式
// luogu-judger-enable-o2 #include<bits/stdc++.h> #define LL long long #define il inline #define re register #define db double #define eps (1e-7) using namespace std; const int N=300000+10; const LL inf=(1ll<<50); il LL rd() { re LL x=0,w=1;re char ch=0; while(ch<'0'||ch>'9') {if(ch=='-') w=-1;ch=getchar();} while(ch>='0'&&ch<='9') {x=(x<<3)+(x<<1)+(ch^48);ch=getchar();} return x*w; } int to[N<<1],nt[N<<1],hd[N],tot=1; il void add(int x,int y) { ++tot,to[tot]=y,nt[tot]=hd[x],hd[x]=tot; ++tot,to[tot]=x,nt[tot]=hd[y],hd[y]=tot; } int n,m,fa[N],sz[N],de[N],dpt[N],hson[N]; int qk[N],qnt[N],qid[N],qhd[N],qt=1; il void qaq(int x,int k,int id){++qt,qk[qt]=k,qnt[qt]=qhd[x],qid[qt]=id,qhd[x]=qt;} LL an[N]; void dfs1(int x) { sz[x]=1; for(int i=hd[x];i;i=nt[i]) { int y=to[i]; if(y==fa[x]) continue; fa[y]=x,dpt[y]=de[y]=de[x]+1,dfs1(y),sz[x]+=sz[y]; if(dpt[hson[x]]<dpt[y]) hson[x]=y; dpt[x]=max(dpt[x],dpt[y]); } } LL *f[N],rbq[N<<1],*px=rbq+1; void dd(int x) { if(hson[x]) f[hson[x]]=f[x]+1,dd(hson[x]); for(int i=hd[x];i;i=nt[i]) { int y=to[i]; if(y==fa[x]||y==hson[x]) continue; f[y]=px,px+=dpt[y]-de[x]+1,dd(y); for(int i=dpt[y]-de[x]-1;i>=0;--i) f[x][i+1]+=f[y][i]; } for(int i=qhd[x];i;i=qnt[i]) { int k=qk[i],id=qid[i]; an[id]=f[x][1]+1ll*(sz[x]-1)*min(de[x]-1,k); if(k+1<=dpt[x]-de[x]) an[id]-=f[x][k+1]; } f[x][0]=f[x][1]+sz[x]-1; } int main() { n=rd(),m=rd(); for(int i=1;i<n;i++) add(rd(),rd()); de[1]=1; dfs1(1); for(int i=1;i<=m;++i) { int p=rd(),k=rd(); qaq(p,k,i); } f[1]=px,px+=dpt[1]; dd(1); for(int i=1;i<=m;++i) printf("%lld\n",an[i]); return 0; }
執行結果(O2):線段樹合併 2022ms,長鏈剖分 942ms
後者不知道比前者高到哪去了,雖然不能和最優解談笑風生