[CF613D]Kingdom and its cities

做题时间:2022.7.8

给你一棵 N(N105) 个点的树,有 q(q105) 次询问,每次询问给出 ki(ki105) 个关键点,询问最少删除树上多少个节点可以使得关键点两两不连通,若不能达成目的输出-1。

第一行一个整数 N
接下来 N1 行每行两个整数表示树上的一条边
2N 行一个整数 q
接下来 q 行每行第一个整数 ki ,接下来 ki 个整数表示本次询问的关键点

q 行,每行一个整数表示答案

虚树、树形DP

非常明显的套路——树上决策、贪心无法解决、每棵子树的答案不和其它点相关,就是树形DP,并且有 ki105 ,同消耗战,非关键点对于答案的影响可以很容易被处理掉,考虑建立虚树进行DP

首先,若有两个关键点 在原树 相邻,则无法达成目的,这里的判断我们留在确定了关键点的DP中进行。

其次,考虑DP,定义 fu 表示以 u 为根的子树的答案。若当前点 u 为关键点,那么 u 就会和其子树中所有当前未拦截的点联通,因此 fu 要加上子树中未拦截的点的数量;若当前点不为关键点,其子树中当前未拦截的点可以通过 u 和另一子树中当前未拦截的点联通,因此要考虑两种情况:

  1. u 的子树中仅有一棵中含有当前未拦截的点,这时其实可以不用拦截,因为它不会和任何点产生联通(若拦截了反而不是最优解);
  2. u 的子树中有多棵中含有当前未拦截的点,这是就必须要拦截了,答案加1即可。

最后加上各个子树的答案即可,转移方程:

fu=vsonufv+{ 0u1 1u2vsonucntvu

最后就是解决虚树与原树在边权上的差异,但实际上除了判断是否能达成目的时要储存下虚树上相邻两点在原数中是否相邻之外,对于DP并没有影响。

#include<cstdio>
#include<iomanip>
#include<algorithm>
using namespace std;
const int N=1e6+50;
struct edge{
int to,nxt,val;
}a[N*10];
int head[N],fa[N],id[N],st[N],top;
int dp[N],dep[N],f[N][25],h[N],cnt;
int n,tot,q,Can,k;
bool Is_h[N],tag[N];
//Is_h[u]表示节点 u 是否为关键节点
//tag[u]表示以 u 为根的子树是否含有未被拦截的关键点
void add(int u,int v,int w)
{
cnt++;
a[cnt].to=v;
a[cnt].val=w;
a[cnt].nxt=head[u];
head[u]=cnt;
}
void DP(int u,int fa)
{
int sum=0;//sum记录u有多少个子树中有未拦截的关键节点
dp[u]=0;
for(int i=head[u];i;i=a[i].nxt){
int v=a[i].to;
if(v!=fa){
if(Is_h[u]&&Is_h[v]&&a[i].val==1) Can=false;
DP(v,u),dp[u]+=dp[v];
if(tag[v]) tag[u]=true,sum++,tag[v]=false;
//若当前儿子v中含有未拦截的关键节点,则记录,顺带清除v的标记
}
}
if(Is_h[u]) dp[u]+=sum,tag[u]=true;//若u是关键点,则要在它将所有包含未拦截关键节点的儿子全部清除
else{
if(sum>1) dp[u]++,tag[u]=false;//若u不是关键点,清除u
}
}
bool cmp(int a,int b){return id[a]<id[b];}
void Swap(int &a,int &b){int t=a;a=b;b=t;}
int LCA(int x,int y)
{
if(dep[x]<dep[y]) Swap(x,y);
for(int i=20;i>=0;i--){
if(dep[f[x][i]]>=dep[y]) x=f[x][i];
}
if(x==y) return x;
for(int i=20;i>=0;i--){
if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
}
return f[x][0];
}
void DFS(int u,int fat)
{
fa[u]=fat;
id[u]=++tot,dep[u]=dep[fat]+1,f[u][0]=fat;
for(int i=1;i<=20;i++) f[u][i]=f[f[u][i-1]][i-1];
for(int i=head[u];i;i=a[i].nxt){
int v=a[i].to;
if(v!=fat) DFS(v,u);
}
}
void Edge(int x,int y)
{
int w=2;
if(fa[x]==y||fa[y]==x) w=1;//若原树中两点相邻
add(x,y,w),add(y,x,w);
}
bool Work()//建立虚树
{
sort(h+1,h+1+k,cmp);
st[top=1]=1,head[1]=0;
for(int i=1;i<=k;i++){
if(h[i]!=1){
int l=LCA(h[i],st[top]);
if(id[l]!=id[st[top]]){
while(id[l]<id[st[top-1]]){
Edge(st[top-1],st[top]);
top--;
}
if(id[l]>id[st[top-1]]){
head[l]=0;
Edge(l,st[top]);
st[top]=l;
}
else Edge(l,st[top--]);
}
head[h[i]]=0,st[++top]=h[i];
}
}
for(int i=1;i<top;i++) Edge(st[i],st[i+1]);
top=0;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n-1;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v,1),add(v,u,1);
}
scanf("%d",&q);
DFS(1,0);
while(q--){
Can=true;
scanf("%d",&k);
for(int i=1;i<=k;i++){
scanf("%d",&h[i]);
Is_h[h[i]]=true;
}
Work();
DP(1,0);
if(!Can) dp[1]=-1;
tag[1]=false,tot=0;
for(int i=1;i<=k;i++) Is_h[h[i]]=false;
printf("%d\n",dp[1]);
}
return 0;
}

本文作者:lxzy

本文链接:https://www.cnblogs.com/Unlimited-Chan/p/16458303.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   lxzy  阅读(24)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.