年前的坑今天补……
求一棵树的带权重心,支持修改权值。
动态树分治,也叫点分树。 就是把每层的重心连成一棵树,然后在这棵树上乱搞(具体网上教程多)。
不过第一次写这题暴力碾过去了…..好像还挺快的….
先讲暴力 假设上一次找到的重心在u,那么如果在某一点v增加了权值,那当前的重心一定是在u到v的相反方向上,只要沿着相反方向找就行了。
具体怎么找…可以这么想: 当前结点为x,y为与x相邻的结点,w[x]为x结点上的权值,cnt为总权值,那么如果cnt-w[y]< w[y]即cnt<2*w[y]时,往y点移动,直到不能移动为止。至于为什么……自己脑补一下
w[x]可以用dfs序加BIT维护。
#include <cstdio>#include <iostream>#include <string>#include <cstring>#define inf 1ll<<60#define N 100010using namespace std;typedef long long ll;int G[N],n,m,tt,rt,l[N],r[N],tc,lcA[N][25],dp[N];ll B[N<<1],Ans,A[N],tot,ds[N];struct edge{ int w,t,nx;}E[N<<1];struct lef{ int f,w,d;}T[N];inline char C(){ static char buf[100000],*p1=buf,*p2=buf; if(p1==p2){ p2=(p1=buf)+fread(buf,1,100000,stdin); if(p1==p2) return EOF; } return *p1++;}inline void reaD(int &x){ char Ch=C();x=0;int f=1; for(;Ch>'9'||Ch<'0';Ch=C())if(Ch=='-')f=-1; for(;Ch>='0'&&Ch<='9';x=x*10+Ch-'0',Ch=C());x*=f;}inline void reaD(ll &x){ char Ch=C();x=0;ll f=1; for(;Ch>'9'||Ch<'0';Ch=C())if(Ch=='-')f=-1; for(;Ch>='0'&&Ch<='9';x=x*10+Ch-'0',Ch=C());x*=f;}inline void InserT(int x,int y,int w){ E[++tt].t=y;E[tt].nx=G[x];E[tt].w=w;G[x]=tt; E[++tt].t=x;E[tt].nx=G[y];E[tt].w=w;G[y]=tt;}inline void build(int g,int f,int d){ T[g].f=f;l[g]=++tc;T[g].d=d; dp[g]=dp[f]+1;ds[g]=ds[f]+d; lcA[g][0]=f; for(int i=1;i<=20;i++) lcA[g][i]=lcA[lcA[g][i-1]][i-1]; for(int i=G[g];i;i=E[i].nx) if(E[i].t!=f) build(E[i].t,g,E[i].w); r[g]=++tc;}inline void add(int x,int y){ for(;x<=tc;x+=x&-x) B[x]+=y;}inline ll query(int x){ ll res=0; for(;x;x-=x&-x) res+=B[x]; return res;}inline ll Qlw(int x){ return query(r[x])-query(l[x]);}int w[30],wt;inline void Pt(ll x){ if(!x){putchar(48);putchar('/n');return;} if(x<0){putchar('-');x=-x;}; while(x)w[++wt]=x%10,x/=10; for(;wt;wt--)putchar(48+w[wt]);putchar('/n');}inline void swap(int &x,int &y){ int z=x;x=y;y=z;}inline int clca(int x,int y){ if(dp[x]<dp[y]) swap(x,y); int delt=dp[x]-dp[y],i; for(i=0;i<=20;i++) if(delt&(1<<i)) x=lcA[x][i]; while(x!=y){ for(i=-1;i;i++) if(lcA[x][i+1]==lcA[y][i+1]) break; if(i==-1) return lcA[x][0]; x=lcA[x][i];y=lcA[y][i]; } return x;}int main(){ freopen("tree.in","r",stdin); freopen("tree.out","w",stdout); reaD(n);reaD(m); for(int i=1,x,y,w;i<n;i++)reaD(x),reaD(y),reaD(w),InserT(x,y,w); /*int ok=0; for(int i=1;i<=n;i++) if(du[i]>2){ok=1;break;} if(!ok){linktime();return 0;}*/ build(1,0,0);reaD(rt); reaD(A[rt]);tot+=A[rt]; add(r[rt],A[rt]); Pt(Ans=0); for(int i=1,x,y,j,lca;i<m;i++){ reaD(x);reaD(y); add(r[x],y);A[x]+=y;tot+=y; lca=clca(x,rt);Ans+=1ll*(ds[x]+ds[rt]-2*ds[lca])*y; //S(rt,0,Ans); while(1){ if(T[rt].f&&tot>2ll*Qlw(rt)){Ans-=1ll*(tot-2*Qlw(rt))*T[rt].d;rt=T[rt].f;continue;} for(j=G[rt];j;j=E[j].nx) if(T[rt].f!=E[j].t&&tot<2ll*Qlw(E[j].t)){ Ans-=1ll*(2*Qlw(E[j].t)-tot)*E[j].w; rt=E[j].t; break; } if(!j) break; } Pt(Ans); } return 0;}复杂度是
正确做法是点分树。 建出点分树,每次找只要从根节点开始分治地找就行了。
复杂度
事实证明如果考场上想不出标算或没时间写标算,这种信仰暴力还是可以接受的……
新闻热点
疑难解答