题意:
给你一棵n个点的树,树上的边有权值>0
然后给你m个询问,每一次询问给你k个点(不包含1(根节点))
让你在树上删除几条边,使得从1出发,无法到达这k个点,并使得删掉的边的权值尽可能小,输出权值
Σki<=500000
解析:
这道题我是先看了虚树,然后再做这道题。
用虚树做的题目有一个很明显的特征就是询问的点的总和(也就是上面的Σki<=500000)是在1e8以内的。
因为虚树的复杂度就是O(询问的点的总和)=O(Σki)=O(500000)
虚树总结一下就是每一次询问,对整棵树进行路径压缩,将没有用的点并掉,
使得只剩下本次询问的点以及本次询问的点之间的lca
。如果本次询问的点是k,那么虚树上最多的点只会是2*k。那么我们对他进行树形dp的时候,在线dp的复杂度就降下来了。
然后这里很关键的一步就是对路径的压缩,怎么压缩是取决于你到底要dp什么东西来决定的。
以这道题举例,我们需要求的是在一棵虚树上,删掉哪些边使得,根节点1与询问点不连通。
如果按正着来的话,每一个点u应该记录的是以u为根的子树中,删除哪些边使得询问的点与u不连通。
那么dp[u]=min(dp[v],mp[u][v]) v∈son(u)
但是这样dp的话,路径压缩过后,我们需要维护的是虚树中父节点与孩子节点的边的路径上的权值最小的边
譬如u在虚树中,v不在,但v的孩子vv在,那么我们就需要计算mp[u][vv]的权值=min(mp[u][v],mp[v][vv])
这样维护就增加了操作。所以对于这道题的dp最好是倒着来,因为每一个点向下对应一棵树,但是向上就只
对应一条链。那么我们就向上维护每一个点到根节点1,路径上的最小值mn[u]。这样压缩过后,因为根节点还是
不变的,所以不需要额外的处理。那么对虚树dp的时候转移方程就是
(现在dp[u]表示删除某些边,使得u的子树内的点不与根节点1连通)
1.如果u是询问点,那么dp[u]=mn[u]
2.否则dp[u]=min(mn[u],Σdp[v]) v∈son(u)
然后虚树还有一点是,我看不同的人写的代码,最后建出来的虚树也是不一样的。
上面两个大佬的代码就不一样,不过其实就是一种情况的不同
他们建出来的树会有一点不一样
询问 4 5 7 8 2点
大佬1:
void insert(int u){
if(top <= 1) {stk[++top] = u;return ;}
int lca = LCA(u,stk[top]);
if(lca == stk[top]) {stk[++top] = u;return ;} //若dfn[y]=dfn[lca],建边
while(top > 1 && dfn[lca] <= stk[top-1]) {
addedge(stk[top-1],stk[top]);
--top;
}
if(lca != stk[top]) stk[++top] = lca;
stk[++top] = u;
}
大佬2:
void insert(int x) {
if(top == 1) {s[++top] = x; return ;}
int lca = LCA(x, s[top]);
if(lca == s[top]) return ; //若dfn[y]=dfn[lca],即y=lca,连边lca−>x,此时子树构建完毕(break);不建边
while(top > 1 && dfn[s[top - 1]] >= dfn[lca]) add_edge(s[top - 1], s[top]), top--;
if(lca != s[top]) add_edge(lca, s[top]), s[top] = lca; //若dfn[y]<dfn[lca],即lca在y,x之间,连边lca−>x,x出栈,再将lca入栈
s[++top] = x;
}
大佬2的代码其实就是把询问的点都变成了叶子节点,因为2也是需要断开的,那么无论4 5 7 8怎么断,最后还是要断1 -2使得
1与2不连通,所以2下面的询问点都是不需要了。每次插入点的时候栈顶一定是询问点!
最后放一个我的代码
// luogu-judger-enable-o2
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <map>
using namespace std;
const int N = 250000+10;
typedef long long ll;
const ll INF = 0x3f3f3f3f3f3f3f3f;
typedef struct node
{
int v;
ll w;
node(int vv=0,ll ww=0):v(vv),w(ww){}
}node;
int pos[N],Log[N<<2],ST[N<<2][25]; //pos[i]:i第一次出现的位置,ST[i][j]在欧拉序[i,i+(1<<j))中dep最小的点
int tot;
//int fa[N][25]; //fa[i][j]记录第i个节点的第(1<<j)个父亲,(非必要)
int dep[N];
vector<node> ee[N];
vector<int> edge[N];
int dfn[N],cnt;
ll mn[N];
void add1(int u,int v,ll w)
{
ee[u].push_back(node(v,w));
}
void add2(int u,int v)
{
edge[u].push_back(v);
}
int Min(int x,int y) {
return dep[x] < dep[y] ? x : y;
}
void dfs(int u,int fc)
{
ST[++tot][0] = u; pos[u] = tot;
dfn[u]=cnt++;
for (int i = 0; i<ee[u].size(); i ++) {
node tmp=ee[u][i];
int v = tmp.v;
if (v == fc) continue;
dep[v] = dep[u] + 1;
mn[v]=min(mn[u],tmp.w); //用于树形dp,路径压缩
dfs(v,u);
ST[++tot][0] = u;//!
}
}
void init(int n)
{
tot=0;
dfs(1,0);
Log[0] = -1;
for (int i = 1; i <= tot; ++i) Log[i] = Log[i >> 1] + 1;
//for (int j = 1; j <= Log[n]; ++j)
// for (int i = 1; i <= n; ++i) fa[i][j] = fa[fa[i][j - 1]][j - 1];
for (int j = 1; j <= Log[tot]; ++j)
for (int i = 1; i <= tot; ++i) ST[i][j] = Min(ST[i][j - 1], ST[i + (1 << (j - 1))][j - 1]);
}
int LCA(int u,int v) {
if (u == v) return u;
u = pos[u], v = pos[v];
if (u > v) swap(u, v);
//u ++; //?
int k = Log[v - u + 1];
return Min(ST[u][k], ST[v - (1 << k) + 1][k]);
}
int stk[N],top;
int que[N];
int mark[N];
bool cmp(int a,int b)
{
return dfn[a]<dfn[b];
}
void insert(int u){ //虚树插入
if(top == 1) {stk[++top] = u;return;}
int lca = LCA(u,stk[top]);
if(lca == stk[top]) {stk[++top] = u;return ;}
while(top > 1 && dfn[lca] <= dfn[stk[top-1]]){
add2(stk[top-1],stk[top]);
--top;
}
if(lca != stk[top]) {
add2(lca,stk[top]);
stk[top] = lca;
}
stk[++top] = u;
}
ll DP(int u)
{
if(edge[u].empty())
{
mark[u]=0; //!
return mn[u];
}
ll sum=0;
for(int v:edge[u])
{
sum+=DP(v);
}
ll ans=mn[u];
if(!mark[u]) ans=min(ans,sum); //放下面是为了下面的删边初始化,不然不好处理
edge[u].clear();
mark[u]=0;
return ans;
}
int main()
{
int n;
scanf("%d",&n);
mn[1]=INF;
cnt=1;
for(int i=1;i<n;i++)
{
int u,v;
ll w;
scanf("%d%d%lld",&u,&v,&w);
add1(u,v,w);
add1(v,u,w);
}
init(n);
int m;
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
int k;
scanf("%d",&k);
top=0;
for(int j=0;j<k;j++)
scanf("%d",&que[j]),mark[que[j]]=1;
sort(que,que+k,cmp);
stk[++top]=1;
for(int j=0;j<k;j++) insert(que[j]);
while(top>1)
add2(stk[top-1],stk[top]),top--;
ll ans=DP(1);
printf("%lld\n",ans);
}
}