http://www.lydsy.com/JudgeOnline/problem.php?id=2286 (题目链接)
一个小小的细节,WA了一天,欲哭无泪了。。
题意:给出一个n个节点的带权树,总共m次询问,每次询问给出K个节点标号,求出切断这些节点与1号节点的路径的最少花费。
solution
构造虚数+树形dp。
首先,有关虚树的题有一个特征,就是题目会给出sigema(k[i])的范围,保证不会太大。所以我们考虑对于每一次询问构造一棵虚树,然后再在虚树上跑dp,可以大大减少复杂度,比如u,v两点之间没有其他询问点,那么我们就可以把uv直接连起来,中间的点是什么我们并不关心。我们只要建出这样一棵树即可:只含当前询问点和它们的lca,并且相对位置关系不变。
举个例子,比如说选1,5,8,10号节点。
那么构造出来的的虚树就是:
那么如何构造虚树呢?
我们先对原树dfs一遍,预处理出dfs序(mark[]),将询问点按照dfs序排序,依次插入一个栈,来动态维护一个叫做“最右链”的东西,就是最右边的一条链……这个也不太好说明,最好自己看着代码模拟一遍……
用虚树还要满足一个条件,就是要维护的信息。例如,和,最大,最小,都有类似于前缀和的性质。就是我们可以从v(u的后代)直接求出u的答案,而不需要遍历u到v的所有边,否则虚树就没有降低复杂度,因为每次还是要在原树上走。在这道题中,我们用一个数组mn[i]来维护节点i到根节点1的路径上的花费最小的那一条边权,mn[i]我们可以在dfs时预处理出。这有什么用呢,看下面。
关于如何在虚树上dp,我么有了mn[]数组后,就变的很简单,f[i]表示断开询问点i以及i的子树上的询问点到根节点1的路径的最小花费。转移方程:f[i]=min(mn[i],sigema(f[e[i].to]),其中e[i].to指的是i节点的孩子节点。
可是我们考虑一种情况,借用上面的图1,若询问点是2和8,mn[8]=4->8=1,mn[2]=1->2=4,按照我们的算法,那么得出的答案就会是1,而这样是不正确的。
也就是说,当存在询问点u,v设deep[u]<deep[v]使lca(u,v)==u时,我们的dp方程是不成立的。对于这种情况,选择切断深度浅的点的mn[u]是最优的。想一想,若切断了mn[u],那么在i的子树上的询问点v自然也被切断了。
所以我们在构建虚树的时候,就预先将这种情况处理掉,也就是在u,v中只选择u放入虚树中。
最后温馨提示:最后输出的答案可能会很大,记得开LL。inf要开到很大,2147483647是远远不够的(博主就这样WA了一天= =)。还有邻接表头head[]不能直接memset,O(mn)的复杂度是承受不了的。
代码:
// bzoj2286 #include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #include<cmath> #define MOD 1000000007 #define inf 1e60 //important #define LL long long #define free(a) freopen(a".in","r",stdin);freopen(a".out","w",stdout); using namespace std; inline int getint() { int x=0,f=1;char ch=getchar(); while (ch>‘9‘ || ch<‘0‘) {if (ch==‘-‘) f=-1;ch=getchar();} while (ch>=‘0‘ && ch<=‘9‘) {x=x*10+ch-‘0‘;ch=getchar();} return x*f; } const int maxn=250010; struct edge {int to,next,w;}e[maxn<<2]; int a[maxn],head[maxn],mark[maxn],deep[maxn],fa[maxn][20],bin[20],s[maxn]; LL f[maxn],mn[maxn]; int n,m,cnt,dfn; void insert(int u,int v,int w) { if (w>0) { e[++cnt].to=v;e[cnt].next=head[u];head[u]=cnt;e[cnt].w=w; e[++cnt].to=u;e[cnt].next=head[v];head[v]=cnt;e[cnt].w=w; } else { if (u==v) return; e[++cnt].to=v;e[cnt].next=head[u];head[u]=cnt; } } void dfs(int u) { mark[u]=++dfn; for (int i=1;i<20;i++) fa[u][i]=fa[fa[u][i-1]][i-1]; for (int i=head[u];i;i=e[i].next) if (e[i].to!=fa[u][0]) { mn[e[i].to]=min(mn[u],(LL)e[i].w); fa[e[i].to][0]=u; deep[e[i].to]=deep[u]+1; dfs(e[i].to); } } bool cmp(int x,int y) { return mark[x]<mark[y]; } int lca(int x,int y) { if (deep[x]<deep[y]) swap(x,y); int t=deep[x]-deep[y]; for (int i=0;bin[i]<=t;i++) if (t&bin[i]) x=fa[x][i]; for (int i=19;i>=0;i--) if (fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i]; return x==y?x:fa[x][0]; } void build(int K) { int tot=0,top=0; cnt=0; a[++tot]=a[1]; for (int i=2;i<=K;i++) if (lca(a[tot],a[i])!=a[tot]) a[++tot]=a[i]; s[++top]=1; for (int i=1;i<=tot;i++) { int t=a[i],x=0; while (top) { x=lca(a[i],s[top]); if (top>1 && deep[x]<deep[s[top-1]]) insert(s[top-1],s[top],0),top--; else if (deep[x]<deep[s[top]]) {insert(x,s[top--],0);break;} else break; } if (s[top]!=x) s[++top]=x; s[++top]=t; } while (--top) insert(s[top],s[top+1],0); } void dp(int u) { LL tmp=0; f[u]=mn[u]; for (int i=head[u];i;i=e[i].next) { dp(e[i].to); tmp+=f[e[i].to]; } head[u]=0; f[u]=tmp==0?mn[u]:min(tmp,mn[u]); } int main() { bin[0]=1;for (int i=1;i<20;i++) bin[i]=bin[i-1]<<1; n=getint(); for (int i=1;i<n;i++) { int u=getint(),v=getint(),w=getint(); insert(u,v,w); } mn[1]=inf;dfs(1); m=getint(); memset(head,0,sizeof(head)); while (m--) { int K=getint(); for (int i=1;i<=K;i++) a[i]=getint(); sort(a+1,a+1+K,cmp); build(K); dp(1); printf("%lld\n",f[1]); } return 0; }