虚树练习集锦

什么是虚树?

比方说有一棵很大很大的树

但是每次会指定一些节点,这些点的个数很少,我们需要在树上进行DP

n的范围会达到百万级别

然而点的个数不会超过1000个

显然很多点都是没有用的点


我们需要把信息浓缩一下,构造出一棵新的树出来


恩。那么哪些会是虚树上的关键节点呢?


除了给定的点外还有一些点的LCA


不会构造怎么办?!


不用担心!构造虚树也是有个模板的



[例题1][Bzoj 2286][Sdoi2011]消耗战


在一场战争中,战场由n个岛屿和n-1个桥梁组成,保证每两个岛屿间有且仅有一条路径可达。现在,我军已经侦查到敌军的总部在编号为1的岛屿,而且他们已经没有足够多的能源维系战斗,我军胜利在望。已知在其他k个岛屿上有丰富能源,为了防止敌军获取能源,我军的任务是炸毁一些桥梁,使得敌军不能到达任何能源丰富的岛屿。由于不同桥梁的材质和结构不同,所以炸毁不同的桥梁有不同的代价,我军希望在满足目标的同时使得总代价最小。
侦查部门还发现,敌军有一台神秘机器。即使我军切断所有能源之后,他们也可以用那台机器。机器产生的效果不仅仅会修复所有我军炸毁的桥梁,而且会重新随机资源分布(但可以保证的是,资源不会分布到1号岛屿上)。不过侦查部门还发现了这台机器只能够使用m次,所以我们只需要把每次任务完成即可。
Input
第一行一个整数n,代表岛屿数量。
接下来n-1行,每行三个整数u,v,w,代表u号岛屿和v号岛屿由一条代价为c的桥梁直接相连,保证1<=u,v<=n且1<=c<=100000。
第n+1行,一个整数m,代表敌方机器能使用的次数。
接下来m行,每行一个整数ki,代表第i次后,有ki个岛屿资源丰富,接下来k个整数h1,h2,…hk,表示资源丰富岛屿的编号。
Output
输出有m行,分别代表每次任务的最小代价。
 


Sample Input
10
1 5 13
1 9 6
2 1 19
2 4 8
2 3 91
5 6 8
7 5 4
7 8 31
10 7 9
3
2 10 6
4 5 7 8 3
3 9 4 6


Sample Output
12
32
22
【数据规模和约定】
对于100%的数据,2<=n<=250000,m>=1,sigma(ki)<=500000,1<=ki<=n-1


#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 500010
 
using namespace std;
 
typedef long long ll;
 
const ll inf = 1LL<<60;
 
int n;
 
struct Edge{
    int to, next, dis;
}edge[maxn];
 
int h[maxn], cnt;
 
void add(int u, int v){
    cnt ++;
    edge[cnt].to = v;
    edge[cnt].next = h[u];
    h[u] = cnt;
}
 
 
struct EG{
    int to, next, dis;
}G[maxn];
 
int hG[maxn], cntG;
 
void addG(int u, int v, int w){
    cntG ++;
    G[cntG].to = v;
    G[cntG].next = hG[u];
    G[cntG].dis = w;
    hG[u] = cntG;
}
 
int root, t[maxn], fa[maxn], dep[maxn], dfn[maxn], dfs_clock;
 
bool cmp(int x, int y){
    return dfn[x] < dfn[y];
}
 
int anc[maxn][20];
 
ll dis[maxn];
 
void dfs(int u){
    dep[u] = dep[fa[u]] + 1;
    dfn[u] = ++ dfs_clock;
    for(int i = hG[u];i;i = G[i].next){
        int v = G[i].to;
        if(v == fa[u])continue;
        dis[v] = min(dis[u], (ll)G[i].dis);
        fa[v] = u;
        dfs(v);
    }
}
 
 
void pre_LCA(){
    for(int i=1;i<=n;i++)
        anc[i][0] = fa[i];
    for(int j=1;1<<j<=n;j++)
        for(int i=1;i<=n;i++){
            int a = anc[i][j-1];
            if(~a)anc[i][j] = anc[a][j-1];
        }
}
 
int ask_LCA(int p, int q){
    if(dep[p] < dep[q])
        swap(p, q);
 
    int log;
    for(log=1;1<<log<=dep[p];log++);log--;
    for(int i=log;i>=0;i--)
        if(~anc[p][i] && dep[anc[p][i]] >= dep[q])
            p = anc[p][i];
    if(p == q)return p;
    for(int i=log;i>=0;i--)
        if(~anc[p][i] && anc[p][i] != anc[q][i])
            p = anc[p][i], q = anc[q][i];
    return fa[p];
}
 
int st[maxn], top;
 
ll dp[maxn];
 
bool mark[maxn];
 
void Tree_DP(int u, bool istrue){
    dp[u] = dis[u];
     
    if(istrue){
        for(int i=h[u];i;i=edge[i].next)
            Tree_DP(edge[i].to, 1);
        mark[u] = false;
        h[u] = 0;
        return;
    }
     
    ll tmp = 0;
    for(int i=h[u];i;i=edge[i].next){
        int v = edge[i].to;
        Tree_DP(v, istrue | mark[v]);
        tmp += dp[v];
    }
    mark[u] = 0;
    if(!h[u] || mark[u])tmp = inf;
    dp[u] = min(dp[u], tmp);
    h[u] = 0;
}
 
int main(){
    scanf("%d", &n);
    int u, v, d;
    for(int i=1; i<n; i++){
        scanf("%d%d%d", &u, &v, &d);
        addG(u, v, d);
        addG(v, u, d);
    }
 
    root = 1;
     
    dis[root] = inf;
 
    dfs(root);
 
    memset(anc, -1, sizeof anc);
 
    pre_LCA();
 
    int test, K;
    scanf("%d", &test);
 
    while(test --){
        scanf("%d", &K);
         
        for(int i=1;i<=K;i++){
            scanf("%d", &t[i]);
            mark[t[i]] = true;
        }
 
        sort(t+1, t+1+K, cmp);
        st[top = 1] = root;
        cnt = 0;
        for(int i=1;i<=K;i++){
            int x = t[i],f = ask_LCA(st[top], x);
            while(dep[f] < dep[st[top]]){
                if(dep[f] >= dep[st[top-1]]){
                    add(f, st[top]);
                    top --;
                    if(f != st[top])st[++ top] = f;
                    break;
                }
                add(st[top-1], st[top]);
                top --;
            }
            if(st[top] != x)st[++ top] = x;
        }
         
        while(-- top)add(st[top], st[top+1]);
         
        Tree_DP(root, 0);
        printf("%lld\n", dp[root]);
    }
 
    return 0;
}



[例题2][Bzoj3611][Heoi2014]大工程

国家有一个大工程,要给一个非常大的交通网络里建一些新的通道。 

我们这个国家位置非常特殊,可以看成是一个单位边权的树,城市位于顶点上。 
在 2 个国家 a,b 之间建一条新通道需要的代价为树上 a,b 的最短路径。
 现在国家有很多个计划,每个计划都是这样,我们选中了 k 个点,然后在它们两两之间 新建 C(k,2)条 新通道。
现在对于每个计划,我们想知道:
 1.这些新通道的代价和
 2.这些新通道中代价最小的是多少 
3.这些新通道中代价最大的是多少
Input
第一行 n 表示点数。
 接下来 n-1 行,每行两个数 a,b 表示 a 和 b 之间有一条边。
点从 1 开始标号。 接下来一行 q 表示计划数。
对每个计划有 2 行,第一行 k 表示这个计划选中了几个点。
 第二行用空格隔开的 k 个互不相同的数表示选了哪 k 个点。
Output


输出 q 行,每行三个数分别表示代价和,最小代价,最大代价。 


Sample Input
10 
2 1 
3 2 
4 1 
5 2 
6 4 
7 5
8 6 
9 7 
10 9 


5 4 

10 4 

5 2 

6 1 

6 1 


Sample Output
3 3 3 
6 6 6
1 1 1 
2 2 2 
2 2 2 
HINT
n<=1000000 
q<=50000并且保证所有k之和<=2*n 


#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define maxn 1000010
using namespace std;
 
typedef long long ll;
 
int n;
 
struct Edge_{
    int to, next;
}G[maxn << 1];
int hG[maxn], cntG;
void addG(int u, int v){
    cntG ++;
    G[cntG].to = v;
    G[cntG].next = hG[u];
    hG[u] = cntG;
}
 
const int root = 1;
 
int fa[maxn], anc[maxn][21];
 
int dep[maxn], dfn[maxn], dfs_clock;
 
 
void dfs(int u){
    dep[u] = dep[fa[u]] + 1;
    dfn[u] = ++ dfs_clock;
    for(int i = hG[u];i; i = G[i].next){
        int v = G[i].to;
        if(v == fa[u])continue;
        fa[v] = u;
        dfs(v);
    }
}
 
bool cmp(const int& x, const int& y){
    return dfn[x] < dfn[y];
}
 
int t[maxn], st[maxn], top;
 
void pre_LCA(){
    for(int i = 1; i <= n; i ++)
        anc[i][0] = fa[i];
    for(int j = 1; 1 << j <= n; j ++)
        for(int i = 1; i <= n; i ++){
            int a = anc[i][j - 1];
            if(a)anc[i][j] = anc[a][j - 1];
        }
}
 
int ask_LCA(int p, int q){
    if(p == 0 || q == 0)return 0;
    if(dep[p] < dep[q])swap(p, q);
    int log;
    for(log = 1; 1 << log <= n; log ++);
    log --;
    for(int i = log; ~i; i --)
        if(anc[p][i] && dep[anc[p][i]] >= dep[q])
            p = anc[p][i];
    if(p == q)return p;
    for(int i = log; ~i; i --)
        if(anc[p][i] && anc[p][i] != anc[q][i])
            p = anc[p][i], q = anc[q][i];
    return fa[p];
}
 
struct Edge{
    int to, next;
    ll dis;
}edge[maxn];
 
int h[maxn], cnt;
 
void add(int u, int v){
    cnt ++;
    edge[cnt].to = v;
    edge[cnt].next = h[u];
    edge[cnt].dis = dep[v] - dep[u];
    h[u] = cnt;
}
 
long long ans1;
 
int ans2, ans3;
 
int size[maxn];
int mx[maxn], mn[maxn];
 
bool mark[maxn];
 
ll dp[maxn];
 
const int inf = 0x7fffffff / 2;
 
void Tree_DP(int u){
    size[u] = mx[u] = 0;
    mn[u] = inf;
    dp[u] = 0;
    ll now = 0;
    for(int i = h[u]; i; i = edge[i].next){
        Tree_DP(edge[i].to);
        size[u] += size[edge[i].to];
    }
     
    size[u] += mark[u];
     
    int max1 = 0, max2 = 0, min1 = inf, min2 = inf;
     
    for(int i = h[u]; i; i = edge[i].next){
        int v = edge[i].to;
        now += (dp[v] + size[v] * edge[i].dis) * (size[u] - size[v]);
        dp[u] += size[v] * edge[i].dis + dp[v];
        mn[u] = min(mn[u], mn[v] + (int)edge[i].dis);
        mx[u] = max(mx[u], mx[v] + (int)edge[i].dis);
        int tmp = mn[v] + edge[i].dis;
        if(!mark[u]){
            if(tmp <= min1){
                min2 = min1;
                min1 = tmp;
            }
            else min2 = min(min2, tmp);
        }
        tmp = mx[v] + edge[i].dis;
        if(tmp >= max1){
            max2 = max1;
            max1 = tmp;
        }
        else max2 = max(max2, tmp);
    }
 
    ans1 += now;
 
    if(mark[u]){
        ans2 = min(ans2, mn[u]);
        mn[u] = 0;
    }
    else ans2 = min(ans2, min1 + min2);
    ans3 = max(ans3, max1 + max2);
    h[u] = 0;
}
 
int main(){
#ifndef ONLINE_JUDGE
    freopen("tree.in", "r", stdin);
    freopen("tree.out", "w", stdout);
#endif
    scanf("%d", &n);
    int u, v;
     
    for(int i = 1; i < n; i++){
        scanf("%d%d", &u, &v);
        addG(u, v);
        addG(v, u);
    }
     
    dfs(root);
    pre_LCA();
     
    int test, K;
    scanf("%d", &test);
    while(test --){
        scanf("%d", &K);
        for(int i = 1; i <= K; i++)
            scanf("%d", &t[i]);
        sort(t + 1, t + 1 + K, cmp);
        int root = t[1];
        for(int i = 2; i <= K; i++)
            root = ask_LCA(t[i], root);
             
        top = cnt = 0;
        st[++ top] = root;
         
        for(int i = 1; i <= K; i ++){
            int x = t[i], f = ask_LCA(st[top], x);
            while(dep[f] < dep[st[top]]){
                if(dep[f] >= dep[st[top - 1]]){
                    add(f, st[top --]);
                    if(f != st[top])
                        st[++ top] = f;
                    break;
                }
                add(st[top - 1], st[top]);
                top --;
            }
            if(st[top] != x)
                st[++ top] = x;
        }
         
        while(-- top)
            add(st[top], st[top + 1]);
             
        ans1 = 0;
        ans2 = n + 1;
        ans3 = 0;
         
        for(int i = 1; i <= K; i ++)
            mark[t[i]] = true;
             
         
        Tree_DP(root);
        printf("%lld %d %d\n", ans1, ans2, ans3);
        for(int i = 1; i <= K; i ++)
            mark[t[i]] = false;
        size[0] = 0;
    }
 
    return 0;
}




[例题3][Codeforces 613D]


D. Kingdom and its Cities
time limit per test
2 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

Meanwhile, the kingdom of K is getting ready for the marriage of the King's daughter. However, in order not to lose face in front of the relatives, the King should first finish reforms in his kingdom. As the King can not wait for his daughter's marriage, reforms must be finished as soon as possible.

The kingdom currently consists of n cities. Cities are connected by n - 1 bidirectional road, such that one can get from any city to any other city. As the King had to save a lot, there is only one path between any two cities.

What is the point of the reform? The key ministries of the state should be relocated to distinct cities (we call such cities important). However, due to the fact that there is a high risk of an attack by barbarians it must be done carefully. The King has made several plans, each of which is described by a set of important cities, and now wonders what is the best plan.

Barbarians can capture some of the cities that are not important (the important ones will have enough protection for sure), after that the captured city becomes impassable. In particular, an interesting feature of the plan is the minimum number of cities that the barbarians need to capture in order to make all the important cities isolated, that is, from all important cities it would be impossible to reach any other important city.

Help the King to calculate this characteristic for each of his plan.

Input

The first line of the input contains integer n (1 ≤ n ≤ 100 000) — the number of cities in the kingdom.

Each of the next n - 1 lines contains two distinct integers uivi (1 ≤ ui, vi ≤ n) — the indices of the cities connected by the i-th road. It is guaranteed that you can get from any city to any other one moving only along the existing roads.

The next line contains a single integer q (1 ≤ q ≤ 100 000) — the number of King's plans.

Each of the next q lines looks as follows: first goes number ki — the number of important cities in the King's plan, (1 ≤ ki ≤ n), then follow exactly ki space-separated pairwise distinct numbers from 1 to n — the numbers of important cities in this plan.

The sum of all ki's does't exceed 100 000.

Output

For each plan print a single integer — the minimum number of cities that the barbarians need to capture, or print  - 1 if all the barbarians' attempts to isolate important cities will not be effective.

Sample test(s)
input
4
1 3
2 3
4 3
4
2 1 2
3 2 3 4
3 1 2 4
4 1 2 3 4
output
1
-1
1
-1
input
7
1 2
2 3
3 4
1 5
5 6
5 7
1
4 2 4 6 7
output
2
Note

In the first sample, in the first and the third King's plan barbarians can capture the city 3, and that will be enough. In the second and the fourth plans all their attempts will not be effective.

In the second sample the cities to capture are 3 and 5.


题目大意:

给定一棵树(n <= 100000)

多组询问(q <= 100000)

给定一些关键节点

最少切断多少条边才能让关键节点两两不连通?


#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 500010
using namespace std;
int n,k;
int now[maxn];
struct Edge{
	int to,next;
}edge[maxn<<1],G[maxn<<1];
int h[maxn],cnt,H[maxn],CNT;
void add(int u,int v){
	cnt++;
	edge[cnt].to=v;
	edge[cnt].next=h[u];
	h[u]=cnt;
}
int fa[maxn],dep[maxn],dfn[maxn],dfs_clock;
const int root=1;
bool cmp(int x,int y){return dfn[x]<dfn[y];}

void re_add(int u,int v){
	CNT++;
	G[CNT].to=v;
	G[CNT].next=H[u];
	H[u]=CNT;
}

void dfs(int u){
	dfn[u]=++dfs_clock;
	dep[u]=dep[fa[u]]+1;
	for(int i=h[u];i;i=edge[i].next){
		int v=edge[i].to;
		if(v==fa[u])continue;
		fa[v]=u;
		dfs(v);
	}
}
int anc[maxn][20];
void pre_LCA(){
	memset(anc,-1,sizeof anc);
	for(int i=1;i<=n;i++)
		anc[i][0]=fa[i];
	for(int j=1;1<<j<=n;j++)
		for(int i=1;i<=n;i++){
			int a=anc[i][j-1];
			if(~a)anc[i][j]=anc[a][j-1];
		}
}
int ask_LCA(int p,int q){
	if(dep[p]<dep[q])swap(p,q);
	int log;
	for(log=1;1<<log<=dep[p];log++);log--;
	for(int i=log;i>=0;i--)
		if(~anc[p][i]&&dep[anc[p][i]]>=dep[q])
			p=anc[p][i];
	if(p==q)return p;
	for(int i=log;i>=0;i--)
		if(~anc[p][i]&&anc[p][i]!=anc[q][i])
			p=anc[p][i],q=anc[q][i];
	return fa[p];
}
int sta[maxn],top;
int mark[maxn],ans;
int dp[maxn][2];
void Tree(int u){
	for(int i=H[u];i;i=G[i].next)
		Tree(G[i].to);
	if(mark[u]){
		dp[u][1]=0;
		if(!fa[u])dp[u][0]=0;
		else dp[u][0]=1;
		for(int i=H[u];i;i=G[i].next){
			int v=G[i].to;
			dp[u][1]+=dp[v][0];
			dp[u][0]+=dp[v][0];
		}
	}
	else{
		dp[u][1]=0;
		dp[u][0]=1;
		int tmp=0,d=0;
		for(int i=H[u];i;i=G[i].next){
			int v=G[i].to;
			tmp+=dp[v][0];
			dp[u][1]+=dp[v][0];
			dp[u][0]+=min(dp[v][0],dp[v][1]);
			d=min(d,dp[v][1]-dp[v][0]);
		}
		dp[u][1]+=d;
		dp[u][0]=min(dp[u][0],tmp);
	}
	H[u]=0;
}
int main(){
	scanf("%d",&n);
	int u,v;
	for(int i=1;i<n;i++){
		scanf("%d%d",&u,&v);
		add(u,v),add(v,u);
	}
	dfs(root);
	pre_LCA();
	int test;
	scanf("%d",&test);
	while(test--){
		scanf("%d",&k);
		for(int i=1;i<=k;i++)
			scanf("%d",&now[i]);
		for(int i=1;i<=k;i++)
			mark[now[i]]=true;
		int flag=true;
		for(int i=1;i<=k;i++)
			if(mark[fa[now[i]]]){
				flag=false;
				break;
			}
		if(flag){
			for(int i=1;i<=k;i++)
				now[i+k]=fa[now[i]];
			k<<=1;
			sort(now+1,now+1+k,cmp);
			k=unique(now+1,now+1+k)-now-1;
			//Build_tree
			sta[++top]=root;
			for(int i=1;i<=k;i++){
				int x=now[i],f=ask_LCA(x,sta[top]);
				while(dep[sta[top]]>dep[f]){
					if(dep[sta[top-1]]<=dep[f]){
						re_add(f,sta[top--]);
						if(sta[top]!=f)sta[++top]=f;
						break;
					}
					re_add(sta[top-1],sta[top]);top--;
				}
				if(x!=sta[top])sta[++top]=x;
			}
			while(--top)re_add(sta[top],sta[top+1]);
			ans=0;
			Tree(root);
			printf("%d\n",min(dp[root][1],dp[root][0]));
		}
		else printf("-1\n");
		CNT=0;
		for(int i=1;i<=k;i++)
			mark[now[i]]=false;
	}

	return 0;
}


虚树+DP

看来要好好学树形DP了。。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值