虚树的构建过程:
因为整个虚树只需要保留特殊点以及他们的LCA节点,所以构建过程如下:
1. 对特殊点的数组按照DFN排序(DFN是dfs到达此节点的时间戳)
2. 对每个排序后相邻的节点求他们的LCA,全部插入到特殊点数组中
3. 对插入了LCA的特殊点数组再次按照DFN排序,之后去重(同一个LCA可能重复出现多次)
4. 初始化空树边。不能用O(n)的时间进行初始化,不然复杂度会退化
5. 使用栈,利用DFN排序之后特殊点数组的欧拉序(DFN和LOW)信息进行建树(LOW是dfs离开此节点的时间戳)
6. 虚树构建完成,根节点为特殊点数组中DFN最小的元素(如果使用了标记,计算虚树的答案之后对标记进行初始化)
题目链接:P2495 [SDOI2011]消耗战
题目描述
在一场战争中,战场由n个岛屿和n-1个桥梁组成,保证每两个岛屿间有且仅有一条路径可达。现在,我军已经侦查到敌军的总部在编号为1的岛屿,而且他们已经没有足够多的能源维系战斗,我军胜利在望。已知在其他k个岛屿上有丰富能源,为了防止敌军获取能源,我军的任务是炸毁一些桥梁,使得敌军不能到达任何能源丰富的岛屿。由于不同桥梁的材质和结构不同,所以炸毁不同的桥梁有不同的代价,我军希望在满足目标的同时使得总代价最小。
侦查部门还发现,敌军有一台神秘机器。即使我军切断所有能源之后,他们也可以用那台机器。机器产生的效果不仅仅会修复所有我军炸毁的桥梁,而且会重新随机资源分布(但可以保证的是,资源不会分布到1号岛屿上)。不过侦查部门还发现了这台机器只能够使用m次,所以我们只需要把每次任务完成即可。
输入输出格式
输入格式:
第一行一个整数n,代表岛屿数量。
接下来n-1行,每行三个整数u,v,w,代表u号岛屿和v号岛屿由一条代价为c的桥梁直接相连,保证
第n+1行,一个整数m,代表敌方机器能使用的次数。
接下来m行,每行一个整数ki,代表第i次后,有ki个岛屿资源丰富,接下来k个整数h1,h2,…hk,表示资源丰富岛屿的编号。
输出格式:
输出有m行,分别代表每次任务的最小代价。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 250005;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int n, m, k, h[MAXN<<1];
int fa[MAXN][20], dfn[MAXN], low[MAXN], dep[MAXN], tot, head[MAXN], cnt;
ll sum[MAXN], mi[MAXN];
struct edge{
int v,w,next;
}e[MAXN<<1];
void add(int u,int v,int w){ e[cnt] = (edge){v,w,head[u]}, head[u] = cnt++; }
inline void dfs(int u,int f,int x,int d){
mi[u] = x, dfn[u] = ++tot, dep[u] = d, fa[u][0] = f;
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].v == f)continue;
dfs(e[i].v, u, min(x,e[i].w), d+1);
} low[u] = tot;
}
inline bool cmp(int a,int b){ return dfn[a] < dfn[b]; }
inline int lca(int u,int v){
if(dep[u]<dep[v])swap(u,v);
for(int d = dep[u] - dep[v], i = 0; d; d>>=1, i ++) if(d&1) u = fa[u][i];
if(u == v)return u;
for(int i = 19;i>=0;i--) if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
return fa[v][0];
}
int stk[MAXN], vis[MAXN];
inline void dfs(int u){
sum[u] = mi[u];
if(vis[u]) return;
sum[u] = 0;
for(int i = head[u]; ~i; i = e[i].next){
dfs(e[i].v);
sum[u] += min(sum[e[i].v], 1ll*mi[e[i].v]);
}
sum[u] = min(1ll*mi[u], sum[u]);
}
int main(){
scanf("%d",&n);
for(int i = 1;i<=n;i++)head[i] = -1; cnt = 0;
for(int i = 1,u,v,w;i<n;i++){
scanf("%d%d%d",&u,&v,&w);
add(u,v,w); add(v,u,w);
}
dfs(1,0,INF,1); //虚树一般只需LCA的信息,dfs之后的图一般不需要保留
scanf("%d",&m);
mi[1] = INF;
while(m--){
scanf("%d",&k);
for(int i = 1; i <= k; i ++)scanf("%d",h+i), vis[h[i]] = 1;
sort(h+1, h+1+k, cmp); //1. DFN排序
for(int i = k; i > 1; i --) h[++k] = lca(h[i],h[i-1]); //2.加LCA,开两倍空间
h[++k] = 1; //有些情况必须加根节点,不加根节点的话h[1]就是根节点
sort(h+1, h+1+k, cmp); k = unique(h+1, h+1+k) - h - 1; //3.再次DFN排序,去重
for(int i = 1; i <= k; i ++) head[h[i]] = -1; cnt = 0; //4.初始化图
for(int i = 1, top = 0; i <= k; i ++){ //5.加边循环
while(top && low[stk[top]] < dfn[h[i]])top--;
if(top) add(stk[top], h[i], 0);
stk[++top] = h[i];
}
dfs(1); printf("%lld\n",sum[1]);
for(int i = 1;i<=k;i++)vis[h[i]] = 0; //6.还原标记
}
return 0;
}
题目链接:CF613D Kingdom and its Cities
题意翻译
一个王国有n座城市,城市之间由n-1条道路相连,形成一个树结构,国王决定将一些城市设为重要城市。
这个国家有的时候会遭受外敌入侵,重要城市由于加强了防护,一定不会被占领。而非重要城市一旦被占领,这座城市就不能通行。
国王定了若干选择重要城市的计划,他想知道,对于每个计划,外敌至少要占领多少个非重要城市,才会导致重要城市之间两两不连通。如果外敌无论如何都不可能导致这种局面,输出-1
输入输出格式
输入格式:
第一行包含一个整数— 表示王国城市的数量。
接下去 行包含两个整数 ,表示第i条道路链接的两个城市编号。
下一行包含一个整数— 表示国王的方案数。
接下去行按一下规则给出:第一个整数 — 表示第个方案中重要城市的个数,然后是个空格隔开的整数,互不相同且范围从1到n—表示这个方案中重要城市的编号。
输出格式:
对于每一个方案输出一个整数,表示最小的需要攻占的个数,如果试图孤立重要城市的企图都无效的话,输出-1。
代码:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN = 250005;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int n,q,k;
struct Graph{;
struct edge{
int v,w,next;
}e[MAXN<<1];
int head[MAXN], cnt;
inline void add(int u,int v){
e[cnt].v = v, e[cnt].next = head[u], head[u] = cnt++;
}
inline void init(){
memset(head,-1,sizeof head); cnt = 0;
}
}S,T;
int h[MAXN], fa[18][MAXN], dep[MAXN], dfn[MAXN], low[MAXN], tot;
bool vis[MAXN];
void dfs(int u,int f,int d){
fa[0][u] = f, dep[u] = d, dfn[u] = ++tot;
for(int i = 1;i<18;i++) fa[i][u] = fa[i-1][fa[i-1][u]];
for(int i = S.head[u];~i;i = S.e[i].next){
if(S.e[i].v == f)continue;
dfs(S.e[i].v,u,d+1);
}low[u] = tot;
}
int lca(int u,int v){
if(dep[u] < dep[v])swap(u,v);
for(int d = dep[u] - dep[v], i = 0; d; d >>= 1, i++) if(d & 1) u = fa[i][u];
if(u == v) return u;
for(int i = 17;i>=0;i--)
if(fa[i][u] != fa[i][v]) u = fa[i][u], v = fa[i][v];
return fa[0][v];
}
inline bool cmp(int a,int b){ return dfn[a] < dfn[b]; }
int stk[MAXN], f[MAXN], g[MAXN];
void dfs(int u){
f[u] = g[u] = 0;//f[u]表示u的子树中答案,g[u]表示u的子树中未截断的特殊点数
for(int i = T.head[u];~i;i = T.e[i].next){
dfs(T.e[i].v);
f[u] += f[T.e[i].v], g[u] += g[T.e[i].v];
}
if(vis[u]) f[u] += g[u], g[u] = 1;
else if(g[u] > 1) f[u] += 1, g[u] = 0;
}
int main(){
scanf("%d",&n);
S.init();
for(int i = 1,u,v;i<n;i++){
scanf("%d %d",&u,&v);
S.add(u,v);S.add(v,u);
}
dfs(1,0,1);
scanf("%d",&q);
while(q--){
scanf("%d", &k);
for(int i = 1;i<=k;i++) scanf("%d",&h[i]), vis[h[i]] = 1;
bool flag = 1;
for(int i = 1;i<=k;i++) if(vis[fa[0][h[i]]])flag = 0;
if(!flag){
for(int i = 1;i<=k;i++) vis[h[i]] = 0;
puts("-1"); continue;
}
sort(h+1,h+1+k,cmp); //1. DFN排序
for(int i = k;i>1;i--)h[++k] = lca(h[i],h[i-1]); //2.加LCA,开两倍空间
sort(h+1,h+1+k,cmp); k = unique(h+1,h+1+k)-h-1; //3.再次DFN排序,去重
for(int i = 1;i<=k;i++) //4.初始化图
T.head[h[i]] = -1, f[h[i]] = g[h[i]] = 0;
for(int i = 1, top = 0;i<=k;i++){ //5.加边循环
while(top && low[stk[top]] < dfn[h[i]])top--;
if(top)T.add(stk[top], h[i]);
stk[++top] = h[i];
}
dfs(h[1]); printf("%d\n", f[h[1]]);
for(int i = 1;i<=k;i++) vis[h[i]] = 0; //6.还原标记
}
return 0;
}