从一道模板题说起
- 一道模板题:[SDOI2011]消耗战
- 一个显然的东西,设f(x)表示x子树内的点不能通过x走到根最小代价。如果x不是特殊点,f(x)=min(m[x], ∑ \sum ∑f(son[x])),否则f(x)=m[x](其中m[x]表示x到根路径上的最短边),然而会T
- 但是我们发现,有很多点是根本没有卵用的。同时,有用的转移,要么是他们本身,要么是几个不存在父子关系的结点的lca,当然m还是非常有卵用的。
- 于是一个牛逼的东西诞生了——虚树
- 虚树就是询问点以及他们两两之间的lca,最多有n-1个新产生的lca
- 虚树的构建过程其实就是我们用一个栈维护一条链,将询问点按照dfs序从小到大加入,设k=lca(y,s[top])
- 假如k=s[top],那么说明y是顺着这条链下去的,那么我们直接加入(但是在这题里面不用,因为下面的询问点就已经没用了)
- 否则,就说明当前栈里,以栈顶为端点的那条链已经完了,当前的y属于新的一条链,那就要将没用的那一部分删去直到dfn[s[top-1]]<dfn[k],然后仍有dfn[s[top]]>=dfn[k],那么我们要判断一下,s[top]是否是k,如果不是,要在虚树里,向s[top]连一条边,并将s[top]赋值为k,最后加入点y
- 注意,将所有点加完之后,栈里面还有一条链,记得要弹出连边。
- code(我感觉luogu的数据有些奇怪,c不是小于等于100000吗)
#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for (int (i)=(a);(i)<=(b);(i)++)
#define Fo(i,x) for (int i=head[x];i;i=next[i])
using namespace std;
const int N=250000+255;
typedef long long ll;
int head[N],to[N*2],next[N*2],w[N*2],n,T,cnt,s[N],k,tot,x,y,z;
int size[N],fa[N],d[N],top[N],son[N],dfn[N],a[N];
ll m[N];
void add(int x,int y){
to[++tot]=y;
next[tot]=head[x];
head[x]=tot;
}
void dfs(int x){
size[x]=1;
Fo(i,x){
int v=to[i];
if (v==fa[x]) continue;
m[v]=min((ll)w[i],m[x]);fa[v]=x;d[v]=d[x]+1;
dfs(v);
size[x]+=size[v];
if (size[son[x]]<size[v]) son[x]=v;
}
}
void Dfs(int x,int T){
dfn[x]=++cnt;
top[x]=T;
if (son[x]) Dfs(son[x],T);else return;
Fo(i,x){
int v=to[i];
if (v==fa[x]||v==son[x]) continue;
Dfs(v,v);
}
}
int lca(int x,int y){
while (top[x]!=top[y]){
if (d[top[x]]<d[top[y]]) swap(x,y);
x=fa[top[x]];
}
return d[x]<d[y]?x:y;
}
void ins(int x){
if (k==1) { s[++k]=x; return; }//这里要加是因为,如果当前点与栈顶的lca是栈顶的话,我们是直接退出的(因为当前点无意义),又因为我们栈里面始终有一个点1压在里面,所以当k=1时,lca(x,s[top])肯定为1,如果没有这句话就加不到了。
int l=lca(s[k],x);
if (l==s[k]) return;
while (k>1&&dfn[s[k-1]]>=dfn[l]) add(s[k-1],s[k]),k--;
if (s[k]!=l) add(l,s[k]),s[k]=l;
s[++k]=x;
}
ll dp(int x){
if (!head[x]) return (ll)m[x];
ll s=0;
Fo(i,x){
s+=dp(to[i]);
}
head[x]=0;
return min(s,(ll)m[x]);
}
bool cmb(int a,int b){
return dfn[a]<dfn[b];
}
int main(){
//freopen("a.in","r",stdin);
//freopen("a.out","w",stdout);
scanf("%d",&n);
fo(i,1,n-1) {
scanf("%d%d%d",&x,&y,&z);
add(x,y);w[tot]=z;
add(y,x);w[tot]=z;
}
m[1]=1ll<<60;
dfs(1);
Dfs(1,1);
memset(head,0,sizeof(head));
scanf("%d",&T);
while (T--){
scanf("%d",&n);
tot=0;
fo(i,1,n) scanf("%d",&a[i]);
sort(a+1,a+n+1,cmb);
s[k=1]=1;
fo(i,1,n) ins(a[i]);
while (k) add(s[k-1],s[k]),k--;
printf("%lld\n",dp(1));
}
return 0;
}
- [HNOI2014]世界树
- 这类题,都有一个特点,就是询问的特殊点特别少,那么我们继续考虑用虚树来做。
- 首先是中规中矩的套路,先把虚树搞出来,然后我们搞虚树上的每个点究竟是被那个点控制的,这个比较简单,dfs两遍就好,注意要先用儿子的更新父亲,再用父亲的更新儿子,不然会错,且将它记为con[x]。
- 然后我们考虑虚树上每一条边(a,b)的答案,不算端点(不然会算重),假设x是距离a最近的点且是a的儿子,且在b到a的路径(原树)上。
- 如果con[a]=con[b],那么原树上这条边对应的这些点都是由con[a]控制的,ans[con[a]]+=size[x]-size[b]
- 否则,我们一定可以在中间找到一个分界点y(倍增实现),使得y以上由con[a]控制,y及以下由con[b]控制,ans[con[a]]+=size[x]-size[y],ans[con[b]=size[y]-size[b]
- 注意到,原树上面会一些我们没有考虑过的子树,那么我们设rem[x]表示x为根的子树中还有多少个没有被计算,那么上面处理边的的时候,就要rem[a]-=size[x],初始的时候rem[x]=size[x],搞完边之后,rem[x]就加给,控制x的那个点即可。
- 另外,为了整棵树都能算到,虚树的根的rem必须为整棵树的size,我们可以强制加入点1。
- code
#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for (int (i)=(a);(i)<=(b);(i)++)
#define fd(i,b,a) for (int (i)=(b);(i)>=(a);(i)--)
#define Fo(i,x) for(int i=head[x];i;i=next[i])
using namespace std;
const int N=300000+5;
int q[N],head[N],to[N*2],next[N*2],con[N],rem[N],ans[N],tot,g[N][21],d[N],size[N];
int n,m,dfn[N],cnt,s[N],top,x,y,a[N],b[N],l1,l2;
void R(int &x){
int t=0,p=1;char ch;
for (ch=getchar();!('0'<=ch&&ch<='9');ch=getchar())
if (ch=='-') p=-1;
for (;('0'<=ch&&ch<='9');ch=getchar())
t=t*10+ch-'0';
x=t*p;
}
bool cmb(int x,int y){
return dfn[x]<dfn[y];
}
void add1(int x,int y){
//printf("%d %d\n",x,y);
to[++tot]=y;
next[tot]=head[x];
head[x]=tot;
}
void add(int x,int y){
to[++tot]=y;
next[tot]=head[x];
head[x]=tot;
// printf("%d %d\n",x,y);
}
void dfs(int x,int y){
size[x]=1;
dfn[x]=++cnt;
Fo(i,x){
int v=to[i];
if (v==y) continue;
d[v]=d[x]+1;g[v][0]=x;
dfs(v,x);
size[x]+=size[v];
}
}
int lca(int x,int y){
if (d[x]<d[y]) swap(x,y);
fd(k,20,0) if (d[g[x][k]]>=d[y]) x=g[x][k];
if (x==y) return x;
fd(k,20,0) if (g[x][k]!=g[y][k]) x=g[x][k],y=g[y][k];
if (x^y) return g[x][0];
return x;
}
int len(int x,int y){
return d[x]+d[y]-2*d[lca(x,y)];
}
void ins(int x){
int k=lca(x,s[top]);
if (k==s[top]){ if (s[top]!=x) s[++top]=x; return; }//注意与上面的区别
while (top>1&&dfn[s[top-1]]>=dfn[k])
add1(s[top-1],s[top]),top--;
if (s[top]^k)
add1(k,s[top]),s[top]=k;
s[++top]=x;
}
void son(int x){
rem[x]=size[x];
q[++q[0]]=x;
Fo(i,x){
int v=to[i];
son(v);
if (!con[v]) continue;
if (!con[x]) {con[x]=con[v]; continue;}
l1=d[con[x]];l2=d[con[v]];
if (l1>l2||(l1==l2)&&(con[x]>con[v])) con[x]=con[v];
}
}
void fa(int x){
Fo(i,x){
int v=to[i];
if (!con[v]) con[v]=con[x];
l1=len(v,con[x]); l2=len(v,con[v]);
if (l1<l2||(l1==l2)&&(con[x]<con[v])) con[v]=con[x];;
fa(v);
}
}
void sol(int a,int b){
int x=b;
fd(k,20,0) if (d[g[x][k]]>d[a]) x=g[x][k];
rem[a]-=size[x];
if (con[a]==con[b]){
ans[con[a]]+=size[x]-size[b];
}
else{
int y=b;
fd(k,20,0){
if (d[g[y][k]]<=d[a]) continue;
l1=len(g[y][k],con[a]);l2=len(g[y][k],con[b]);
if (l1>l2||(l1==l2)&&(con[a]>con[b])) y=g[y][k];
}
ans[con[a]]+=size[x]-size[y];
ans[con[b]]+=size[y]-size[b];
}
}
int main(){
//freopen("a.in","r",stdin);
//freopen("a.out","w",stdout);
R(n);
fo(i,1,n-1) R(x),R(y),add(x,y),add(y,x);
g[1][0]=1;
dfs(1,1);
fo(j,1,20) fo(i,1,n) g[i][j]=g[g[i][j-1]][j-1];
R(m);
memset(head,0,sizeof(head));
while (m--){
tot=0;
R(n);
fo(i,1,n) R(a[i]),b[i]=a[i],con[a[i]]=a[i];
sort(a+1,a+n+1,cmb);
s[top=1]=1;
fo(i,1,n)
ins(a[i]);
while (top>1) add1(s[top-1],s[top]),top--;
q[0]=0;
son(s[1]);fa(s[1]);
fo(i,1,q[0]) {
Fo(j,q[i]){
sol(q[i],to[j]);
}
}
fo(i,1,q[0]) ans[con[q[i]]]+=rem[q[i]];
fo(i,1,n) printf("%d ",ans[b[i]]);
printf("\n");
fo(i,1,q[0]) ans[q[i]]=head[q[i]]=con[q[i]]=rem[q[i]]=0;
}
return 0;
}
后面的两道题,可能会等哪天复习虚树的时候在做。
[SDOI2015]寻宝游戏
[HEOI2014]大工程