暴力dp非常显然,设f[i][0/1]表示i号点不选/选时i子树内的答案,则f[i][0]=Σf[son][1],f[i][1]=a[i]+Σmin(f[son][0],f[son][1])。
注意到B的部分分,可以想到每次修改只会对修改点到根的路径上的点的dp值产生影响。
考虑如何优化修改路径这一过程,先看只修改一个点的情况。
由于每次修改并非累积,事实上应该考虑一种预处理来快速得到答案,并且发现其实最终我们只需要f[root][]。容易想到倍增。设f[x][k][0/1][0/1]表示x号点为0/1时其2k级祖先为0/1时这条链上的答案,即其2k级祖先的子树-x的子树的答案。这个东西本身就是可减的,即知道了在x号点子树内的y点选/不选的情况下x子树的答案、y号点选/不选的情况下y子树的答案,将其相减就是x子树去掉y子树的答案。
倍增数组并不难求,显然我们已经有f[x][0][][],在2k-1级祖先那里合并得到2k级的答案,考虑2k-1级祖先选还是不选取个min即可,大约就是floyd/矩乘。回答询问同样也是类似的很正常的倍增。那么只改一个点就能做了。
再考虑改两个点,其实基本类似。两个点倍增求出到他们的lca下方一个点的答案,以此更新lca答案,再从lca倍增跳到根即可。对其中一点是另一点祖先的情况最好特判。听起来不是很复杂但写起来得考虑清楚。
另一种做法是ddp,暂时觉得不太学的动,好像也很久没学新姿势了。感觉倍增做法看上去还是比较noip的,虽然考场上被神仙t2和完全没碰过但知道能做这个题的ddp冲昏头脑肯定想不出来。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; #define ll long long #define inf 100000000000ll #define N 100010 char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;} int gcd(int n,int m){return m==0?n:gcd(m,n%m);} int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int n,m,a[N],p[N],fa[N][18],deep[N],t; ll f[N][2],g[N][18][2][2]; struct data{int to,nxt,len; }edge[N<<1]; struct data2{ll x,y;int id;}; void addedge(int x,int y){t++;edge[t].to=y,edge[t].nxt=p[x],p[x]=t;} void dfs(int k) { f[k][0]=0,f[k][1]=a[k]; for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=fa[k][0]) { deep[edge[i].to]=deep[k]+1; fa[edge[i].to][0]=k; dfs(edge[i].to); f[k][0]+=f[edge[i].to][1]; f[k][1]+=min(f[edge[i].to][0],f[edge[i].to][1]); } for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=fa[k][0]) { g[edge[i].to][0][0][0]=inf; g[edge[i].to][0][0][1]=f[k][1]-min(f[edge[i].to][0],f[edge[i].to][1]); g[edge[i].to][0][1][0]=f[k][0]-f[edge[i].to][1]; g[edge[i].to][0][1][1]=f[k][1]-min(f[edge[i].to][0],f[edge[i].to][1]); } } void pre() { for (int j=1;j<18;j++) { for (int i=1;i<=n;i++) fa[i][j]=fa[fa[i][j-1]][j-1]; for (int i=1;i<=n;i++) for (int x=0;x<2;x++) for (int y=0;y<2;y++) g[i][j][x][y]=min(g[i][j-1][x][0]+g[fa[i][j-1]][j-1][0][y],g[i][j-1][x][1]+g[fa[i][j-1]][j-1][1][y]); } } int lca(int x,int y) { if (deep[x]<deep[y]) swap(x,y); for (int j=17;~j;j--) if (deep[fa[x][j]]>=deep[y]) x=fa[x][j]; if (x==y) return x; for (int j=17;~j;j--) if (fa[x][j]!=fa[y][j]) x=fa[x][j],y=fa[y][j]; return fa[x][0]; } data2 query(int x,int root,int isup,ll tx,ll ty) { data2 u;u.x=tx,u.y=ty; for (int j=17;~j;j--) if (deep[fa[x][j]]>deep[root]) { tx=u.x,ty=u.y; u.x=min(tx+g[x][j][0][0],ty+g[x][j][1][0]); u.y=min(tx+g[x][j][0][1],ty+g[x][j][1][1]); x=fa[x][j]; } u.id=x; if (isup) { tx=u.x,ty=u.y; u.x=min(tx+g[x][0][0][0],ty+g[x][0][1][0]); u.y=min(tx+g[x][0][0][1],ty+g[x][0][1][1]); } return u; } int main() { #ifndef ONLINE_JUDGE freopen("bzoj5466.in","r",stdin); freopen("bzoj5466.out","w",stdout); const char LL[]="%I64d "; #else const char LL[]="%lld "; #endif n=read(),m=read();read(); for (int i=1;i<=n;i++) a[i]=read(); for (int i=1;i<n;i++) { int x=read(),y=read(); addedge(x,y),addedge(y,x); } fa[1][0]=1;dfs(1); pre(); while (m--) { int x=read(),opx=read(),y=read(),opy=read(); if (deep[x]<deep[y]) swap(x,y),swap(opx,opy); if (opx==0&&opy==0&&fa[x][0]==y) {printf("-1 ");continue;} int k=lca(x,y); if (k==y) { data2 u=query(x,k,0,opx==0?f[x][0]:inf,opx==1?f[x][1]:inf); ll tx=u.x,ty=u.y; u.x=f[k][0]+ty-f[u.id][1]; u.y=f[k][1]+min(tx,ty)-min(f[u.id][0],f[u.id][1]); if (opy==0) u.y=inf;else u.x=inf; if (k!=1) u=query(k,1,1,u.x,u.y); printf(LL,min(u.x,u.y)); } else { data2 u=query(x,k,0,opx==0?f[x][0]:inf,opx==1?f[x][1]:inf); data2 v=query(y,k,0,opy==0?f[y][0]:inf,opy==1?f[y][1]:inf); data2 w; w.x=f[k][0]+u.y-f[u.id][1]+v.y-f[v.id][1]; w.y=f[k][1]+min(u.x,u.y)-min(f[u.id][0],f[u.id][1])+min(v.x,v.y)-min(f[v.id][0],f[v.id][1]); w=query(k,1,1,w.x,w.y); printf(LL,min(w.x,w.y)); } } return 0; }