第一行一个整数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,表示资源丰富岛屿的编号。
输出有m行,分别代表每次任务的最小代价。
对于100%的数据,2<=n<=250000,m>=1,sigma(ki)<=500000,1<=ki<=n-1
题意:给一棵树,每条边有个权值,然后又M个询问,每个询问给出K个点代表有资源,然后需要去掉若干条边使得1号点与资源点不联通,问去掉的边权总和最小值。每个询问是独立的。
思路:首先考虑树形dp的做法,预处理每个点到根节点的最小的一条边imin[],dp[i]表示断开根节点与i的子树的资源点最小花费,dp[i] = min(imin[i], sigma(dp[j])),j为i的儿子,复杂度O(n)。但是显然不能每个询问都跑O(n)次,观察到总询问的资源点数<=500000,能否每个询问只对那K个点进行dp呢?这就是所谓的虚树,本题一棵虚数包含K个点及他们的所有LCA。建虚树用栈实现,先对K个点按DFS序排序,建虚树时就能保证同一个栈中的点构成一条链,具体见代码。另外本题假如某个资源点i到根节点路径中存在其他资源点j1,j2,j3...,那么显然虚树的根节点取到深度最浅的那个j就行。
# include <iostream>
# include <cstring>
# include <vector>
# include <cstdio>
# include <algorithm>
# define pb push_back
# define mp make_pair
using namespace std;
typedef long long LL;
const int maxn = 3e5+30;
const LL INF = 1LL<<60;
vector<pair<int,int> >G[maxn];
vector<int>g[maxn];
int cnt=0, n, id[maxn], h[maxn], fa[maxn][22];
int a[maxn], st[maxn*2], sta[maxn];
LL dp[maxn], imin[maxn];
void dfs(int cur, int pre)
{
h[cur] = h[pre] + 1;
id[cur] = ++cnt;
fa[cur][0] = pre;
for(int i=1; (1<<i)<=h[cur]; ++i)
fa[cur][i] = fa[fa[cur][i-1]][i-1];
for(int i=0; i<G[cur].size(); ++i)
{
int v = G[cur][i].first;
if(v != pre)
{
imin[v] = min(imin[cur], 1LL*G[cur][i].second);
dfs(v, cur);
}
}
}
void dfs2(int cur)
{
dp[cur] = imin[cur];
LL s = 0;
for(int i=0; i<g[cur].size(); ++i)
{
int v = g[cur][i];
dfs2(v);
s += dp[v];
}
if(s && s < dp[cur]) dp[cur] = s;
}
int lca(int v, int u)
{
if(h[v] > h[u]) swap(v, u);
for(int i=0; i<20; ++i)
if(h[u]-h[v]>>i & 1)
u = fa[u][i];
for(int i=20; i>=0; --i)
if(fa[v][i] != fa[u][i])
v = fa[v][i], u=fa[u][i];
return v == u?v:fa[v][0];
}
bool cmp(int x, int y){return id[x] < id[y];}
void link(int u, int v, int tag)
{
if(u == v) return;
if(sta[u] != tag) sta[u] = tag, g[u].clear();
if(sta[v] != tag) sta[v] = tag, g[v].clear();
g[u].pb(v);
}
void work(int tag)
{
int tot=1, top=1, k;
scanf("%d",&k);
for(int i=1; i<=k; ++i) scanf("%d",&a[i]);
sort(a+1, a+k+1, cmp);
for(int i=2; i<=k; ++i)
if(lca(a[tot], a[i]) != a[tot])
a[++tot] = a[i];
st[top] = 1;
for(int i=1; i<=tot; ++i)
{
int l = lca(st[top], a[i]);
while(1)
{
if(h[l] >= h[st[top-1]])
{
link(l, st[top--], tag);
break;
}
link(st[top-1], st[top], tag); --top;
}
if(st[top] != l) st[++top] =l ;
if(st[top] != a[i]) st[++top] = a[i];
}
while(--top) link(st[top], st[top+1], tag);
dfs2(1);
printf("%lld\n",dp[1]);
}
int main()
{
int u, v, w, m;
scanf("%d",&n);
for(int i=1; i<n; ++i)
{
scanf("%d%d%d",&u,&v,&w);
G[u].pb(mp(v,w));
G[v].pb(mp(u,w));
}
imin[1] = INF; h[0] = -1;
dfs(1, 0);
scanf("%d",&m);
while(m--) work(m);
return 0;
}
本文介绍了一种在树形结构的战场上,针对敌军可能获取的能源点实施封锁的算法。通过树形DP预处理与虚树构建技术,实现了对多次资源分布变化的有效应对。
210

被折叠的 条评论
为什么被折叠?



