水平不够,学习来凑
又开了个天大的新坑…
例题:
树形DP:
题目大意就是讲:给出一棵树,有边权,然后给出K个查询点,问从1号店不能到任何一个查询点的代价是多少.
先考虑一下树形动归.
dp[i]表示从1不能到以i为根的子树中的所有查询点的最小代价
考虑维护一个量,mins[i]表示从1到i路径最小权值
dp[i]显然要通过枚举子树转移
如果i不是叶子的话,那么
dp[i]=min(∑jdp[j],mins[j])
d
p
[
i
]
=
m
i
n
(
∑
j
d
p
[
j
]
,
m
i
n
s
[
j
]
)
j为i的子树邻接点
显然每次得到dp[1]是要花费O(n)的代价的 只有40pts…
虚树优化:
如果是上面的树形DP,那么总的复杂度就是 O(n*m)
然而题目里告诉我们
∑mki<=500000
∑
k
i
m
<=
500000
,就要考虑在这下手了
如果我们可以发现,在转移的时候很多点都是无用的,其实有用的点就只有查询点和一些他们的LCA
那么我们就可以考虑重新构造出一棵树,每个节点的维护值与原树等价,但是深度很低,我们把这个树称为虚树。
虚树上的点一定是<=2*k的
这样这题就可做了
构造虚树:
首先我们需要一个单调栈来维护一条从根节点延伸下来的链,然后我们有四个节点。
1:当前点
2:栈顶
3:次栈顶
4:lca(当前点,栈顶)
考虑点4,有两种可能。
1:点4在点3的上方,那么点3向点2俩边,点2出栈。
2:点4在点2和点3的中间,那么点4向点2连边,点2出栈,点4入栈,链底部显然是点1,所以点1入栈。
于是我们就构造出了一颗虚树。
假设查询点为 5、8、10,那么虚树大概是长这个样子
实现的一些小细节:
1:求LCA树剖不知道比倍增快到哪去了
2:我们对于每次的查询重新构造时,并不需要每次都初始化head数组,而是可以在上次的树遍历里直接初始化(要不T成SB
例题代码:
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define int long long
const int maxm=251000;
int head[maxm],to[maxm<<1],net[maxm<<1],cost[maxm<<1];
int son[maxm],siz[maxm],fa[maxm],top[maxm],dfn[maxm],deep[maxm],mins[maxm];
int h[maxm],stk[maxm],tops;
int dp[maxm];
bool vis[maxm];
int tot,cnt;
int n,q;
inline void addedge(int u,int v,int c)
{
if(u==v) return;
cnt++;
to[cnt]=v,cost[cnt]=c,net[cnt]=head[u],head[u]=cnt;
}
inline bool comp(int a,int b)
{
return dfn[a]<dfn[b];
}
void dfs1(int now,int fax,int dep)
{
dfn[now]=++tot,deep[now]=dep,fa[now]=fax,siz[now]=1;
for(int i=head[now];i;i=net[i])
if(to[i]!=fax)
{
mins[to[i]]=std::min(mins[now],cost[i]);
dfs1(to[i],now,dep+1);
siz[now]+=siz[to[i]];
if(siz[to[i]]>siz[son[now]]) son[now]=to[i];
}
}
void dfs2(int now,int topx)
{
top[now]=topx;
vis[now]=1;
if(son[now]) dfs2(son[now],topx);
for(int i=head[now];i;i=net[i])
if(!vis[to[i]])
dfs2(to[i],to[i]);
}
inline int LCA(int u,int v)
{
while(top[u]!=top[v])
{
if(deep[top[u]]<deep[top[v]]) std::swap(u,v);
u=fa[top[u]];
}
return deep[u]<deep[v]?u:v;
}
void DFS(int now)
{
//printf("-%d-\n",now);
int ans=0;
for(int i=head[now];i;i=net[i])
{
DFS(to[i]);
ans+=dp[to[i]];
}
head[now]=0;
dp[now]=ans?std::min(ans,mins[now]):mins[now];
}
void buildtree()
{
cnt=0;
int k;
scanf("%lld",&k);
for(int i=1;i<=k;i++)
scanf("%lld",&h[i]);
std::sort(h+1,h+k+1,comp);
int num=1;
for(int i=2;i<=k;i++)
if(LCA(h[num],h[i])!=h[num]) h[++num]=h[i];
stk[tops=1]=1;
for(int i=1;i<=num;i++)
{
int now=h[i],lca=LCA(now,stk[tops]);
while(1)
{
if(deep[lca]>=deep[stk[tops-1]])
{
addedge(lca,stk[tops--],0);
if(lca!=stk[tops])stk[++tops]=lca;
break;
}
addedge(stk[tops-1],stk[tops],0);
tops--;
}
if(stk[tops]!=now)stk[++tops]=now;
}
while(--tops) addedge(stk[tops],stk[tops+1],0);
DFS(1);
printf("%lld\n",dp[1]);
}
main()
{
scanf("%lld",&n);
for(int i=1;i<n;i++)
{
int u,v,c;
scanf("%lld%lld%lld",&u,&v,&c);
addedge(u,v,c),addedge(v,u,c);
}
mins[1]=1e60;
dfs1(1,0,0),dfs2(1,1);
//for(int i=1;i<=n;i++) printf("%d\n",mins[i]);
memset(head,0,sizeof(head));
scanf("%lld",&q);
while(q--)
buildtree();
return 0;
}
题目集锦:
暂无…