膜拜一发\(mts\_246,forever\_shi\)

这两位爷是真的无敌!

首先来看这个题,一看题目的数据范围和“关键点”字眼,我们就能得知这是一道虚树题

那就先一如既往的建出来虚树吧

QWQ

但是这之后,应该怎么去dp呢。

首先,我们需要知道在虚树上每个点的从属都是谁,这样才便于我们进一步扩展到虚树之外的点。

那么怎么求这个东西呢?我们可以先通过一编dfs,求出来子树对父亲的影响,也就是从下到上的答案(先\(dfs\)到底,再更新)

void dp1(int x,int flag)
{
dis[x]=inf;
bel[x]=0;
ans[x]=0;
if (tag[x]==flag)
{
dis[x]=0;
bel[x]=x;
}
for (int i=point[x];i;i=nxt[i])
{
int p = to[i];
int now = val[i];
dp1(p,flag);
if ((dis[x]>dis[p]+val[i]) || (dis[x]==dis[p]+val[i] && bel[x]>bel[p]))
{
dis[x]=dis[p]+val[i];
bel[x]=bel[p];
}
}
}

然后呢,因为还存在说通过兄弟更新,或者子树之外的点更新的情况,所以我们还需要重新\(dfs\)一遍,不过这次是尝试通过用父亲来更新儿子,也就是从上到下(先更新,后\(dfs\))

void dp2(int x,int flag)
{
for (int i=point[x];i;i=nxt[i])
{
int p = to[i];
int now = val[i];
if ((dis[p]>dis[x]+val[i]) || (dis[p]==dis[x]+val[i] && bel[p]>bel[x]))
{
dis[p]=dis[x]+val[i];
bel[p]=bel[x];
}
dp2(p,flag);
}
}

至此,我们就得到了所有虚树上的点的\(dis\) 和 \(bel\),那怎么扩展到所有点呢QWQ

这里就需要一个奇妙的统计答案的技巧了

我们另\(ymh[i]\)表示与\(i\)相同议事处的点的个数。

首先,我们将初值弄成\(size[i]\),是i在原树的子树大小(这一定是不对的,因为子树中有一些会和他的某个非直系子辈给包含,而他在上面的一片区域,也一定有和他一样的点)

然后我们进行dfs

对于这个东西,显然是要从下向上更新的

所以我们\(dfs\)到底,对于当前\(x->p\)这条边,如果说两个点的\(bel\)是相等的,我们就令\(ymh[x]-=size[p]\),相当于把原树\(x->p\)这路径附近部分所有的点,都给了\(x\),不论是合法还是不合法。

那么上一种情况里面不合法的情况,就是两个点之间存在\(bel\)不一样的点,也就是说,会存在一条边\(x->p\),其中\(bel[x]!=bel[p]\),那么这条路径之间的东西应该怎么算呢。

不难发现,一定是会存在说,这段路径中间会有一个点,以上全是属于\(bel[x]\),以下全是属于\(bel[p]\)的。

那么我们可以通过倍增的方式来求出这个点(具体求的时候有一些细节,直接写在代码里面了)

然后假设求出来的点是\(lyf\),那么$$ymh[p]+=size[lyf]-size[p],ymh[x]-=size[lyf]$$

原理的话,和上面同理

这种用ymh数组求解的方式,实际上就是先弄一个初值,然后把不合法的(或者是会算重复的)减掉,然后把少算的加进去

QWQ总之就是很巧妙!!!!!!!

既不会算少,也不会算重复

直接放代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define mk makr_pair
#define ll long long
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
const int maxn = 6e5+1e2;
const int maxm = 2*maxn;
const int inf = 1e9;
int point[maxn],nxt[maxm],to[maxm],val[maxm];
int bel[maxn],dis[maxn],f[maxn][21];
int num[maxn];
int size[maxn],deep[maxn],dfn[maxn];
int cnt,n,m;
int tot,top;
int s[maxn];
int k,a[maxn];
int ymh[maxn],tag[maxn];
int ans[maxn];
void addedge(int x,int y,int w)
{
nxt[++cnt]=point[x];
to[cnt]=y;
val[cnt]=w;
point[x]=cnt;
}
void dfs(int x,int fa,int dep)
{
deep[x]=dep;
dfn[x]=++tot;
size[x]=1;
for (int i=point[x];i;i=nxt[i])
{
int p = to[i];
if (p==fa) continue;
f[p][0]=x;
dfs(p,x,dep+1);
size[x]+=size[p];
}
}
void init()
{
for (int j=1;j<=20;j++)
for (int i=1;i<=n;i++)
{
f[i][j]=f[f[i][j-1]][j-1];
}
}
int go_up(int x,int d)
{
for (int i=0;i<=20;i++)
{
if (d & (1<<i))
x=f[x][i];
}
return x;
}
int lca(int x,int y)
{
if (deep[x]>deep[y]) x=go_up(x,deep[x]-deep[y]);
else y=go_up(y,deep[y]-deep[x]);
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];
}
bool cmp(int a,int b)
{
return dfn[a]<dfn[b];
}
void solve()
{
sort(a+1,a+1+k,cmp);
cnt=0;
top=1;
s[top]=1;
for (int i=1;i<=k;i++)
{
int l = lca(s[top],a[i]);
if (l!=s[top])
{
while (top>1)
{
if (dfn[s[top-1]]>dfn[l])
{
addedge(s[top-1],s[top],deep[s[top]]-deep[s[top-1]]);
top--;
}
else
{
if (dfn[s[top-1]]==dfn[l])
{
addedge(s[top-1],s[top],deep[s[top]]-deep[s[top-1]]);
top--;
break;
}
else
{
addedge(l,s[top],deep[s[top]]-deep[l]);
s[top]=l;
break;
}
}
}
}
if (s[top]!=a[i]) s[++top]=a[i];
}
while (top>1)
{
addedge(s[top-1],s[top],deep[s[top]]-deep[s[top-1]]);
top--;
}
}
void dp1(int x,int flag)
{
dis[x]=inf;
bel[x]=0;
ans[x]=0;
if (tag[x]==flag)
{
dis[x]=0;
bel[x]=x;
}
for (int i=point[x];i;i=nxt[i])
{
int p = to[i];
int now = val[i];
dp1(p,flag);
if ((dis[x]>dis[p]+val[i]) || (dis[x]==dis[p]+val[i] && bel[x]>bel[p]))
{
dis[x]=dis[p]+val[i];
bel[x]=bel[p];
}
}
}
void dp2(int x,int flag)
{
for (int i=point[x];i;i=nxt[i])
{
int p = to[i];
int now = val[i];
if ((dis[p]>dis[x]+val[i]) || (dis[p]==dis[x]+val[i] && bel[p]>bel[x]))
{
dis[p]=dis[x]+val[i];
bel[p]=bel[x];
}
dp2(p,flag);
}
}
int up(int x,int d)
{
for (int i=20;i>=0;i--)
if (deep[f[x][i]]>=d) x=f[x][i];
return x;
}
void dodo(int x)
{
ymh[x]=size[x];
for (int &i=point[x];i;i=nxt[i])
{
int p = to[i];
dodo(p);
if (bel[x]==bel[p]) ymh[x]-=size[p];
else
{
int now = dis[p]+dis[x]+deep[p]-deep[x]-1; //这里减1的原因是为了后面方便一些,因为偶数的情况,中间那个点的归属不能够直接倍增的时候判断,所以我们需要在后面if的时候,特殊处理一下
int st = now/2-dis[p]; //这个是距离to的距离
int dd = deep[p]-st; //中间点的深度
int lyf = p;
if(dd>=0) lyf=up(p,dd);
if ((now&1) && bel[x]>bel[p] && st>=0) lyf = f[lyf][0]; //与上面那个减1相对应,判断中间点的归属
ymh[p]+=size[lyf]-size[p]; //把lyf底下的点,都给to
ymh[x]-=size[lyf]; //把转折点剩下的部分给fa,由于初值是整个的size,所以直接做减法就好
}
ans[bel[p]]+=ymh[p];
}
if (x==1) ans[bel[x]]+=ymh[x];
}
int b[maxn];
int main()
{
n=read();
for (int i=1;i<n;i++)
{
int x=read(),y=read();
addedge(x,y,1);
addedge(y,x,1);
}
dfs(1,0,1);
init();
memset(point,0,sizeof(point));
m=read();
for (int i=1;i<=m;i++)
{
k=read();
for (int j=1;j<=k;j++) a[j]=read(),tag[a[j]]=i,b[j]=a[j];
solve();
dp1(1,i);
dp2(1,i);
dodo(1);
for (int j=1;j<=k;j++) cout<<ans[b[j]]<<" ";
cout<<"\n";
for (int j=1;j<=k;j++) ans[b[j]]=0;
}
return 0;
}

洛谷3233 HNOI2014(虚树+dp)的更多相关文章

  1. 洛谷P2495 [SDOI2011]消耗战(虚树dp)

    P2495 [SDOI2011]消耗战 题目链接 题解: 虚树\(dp\)入门题吧.虚树的核心思想其实就是每次只保留关键点,因为关键点的dfs序的相对大小顺序和原来的树中结点dfs序的相对大小顺序都是 ...

  2. BZOJ 3572 [HNOI2014]世界树 (虚树+DP)

    题面:BZOJ传送门 洛谷传送门 题目大意:略 细节贼多的虚树$DP$ 先考虑只有一次询问的情况 一个节点$x$可能被它子树内的一个到x距离最小的特殊点管辖,还可能被管辖fa[x]的特殊点管辖 跑两次 ...

  3. bzoj 3572世界树 虚树+dp

    题目大意: 给一棵树,每次给出一些关键点,对于树上每个点,被离它最近的关键点(距离相同被标号最小的)控制 求每个关键点控制多少个点 分析: 虚树+dp dp过程如下: 第一次dp,递归求出每个点子树中 ...

  4. bzoj 2286 [Sdoi2011]消耗战 虚树+dp

    题目大意:多次给出关键点,求切断边使所有关键点与1断开的最小费用 分析:每次造出虚树,dp[i]表示将i和i子树与父亲断开费用 对于父亲x,儿子y ①y为关键点:\(dp[x]\)+=\(dismn( ...

  5. 【BZOJ】2286: [Sdoi2011]消耗战 虚树+DP

    [题意]给定n个点的带边权树,每次询问给定ki个特殊点,求隔离点1和特殊点的最小代价.n<=250000,Σki<=500000. [算法]虚树+DP [题解]考虑普通树上的dp,设f[x ...

  6. 洛谷P3373 [模板]线段树 2(区间增减.乘 区间求和)

    To 洛谷.3373 [模板]线段树2 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.将某区间每一个数乘上x 3.求出某区间每一个数的和 输入输出格式 输入格 ...

  7. 【BZOJ2830/洛谷3830】随机树(动态规划)

    [BZOJ2830/洛谷3830]随机树(动态规划) 题面 洛谷 题解 先考虑第一问. 第一问的答案显然就是所有情况下所有点的深度的平均数. 考虑新加入的两个点,一定会删去某个叶子,然后新加入两个深度 ...

  8. [BZOJ5287][HNOI2018]毒瘤(虚树DP)

    暴力枚举非树边取值做DP可得75. 注意到每次枚举出一个容斥状态的时候,都要做大量重复操作. 建立虚树,预处理出虚树上两点间的转移系数.也可动态DP解决. 树上倍增.动态DP.虚树DP似乎是这种问题的 ...

  9. [BZOJ2286][SDOI2011]消耗战(虚树DP)

    2286: [Sdoi2011]消耗战 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 4998  Solved: 1867[Submit][Statu ...

随机推荐

  1. redis rpoplpush列表转移元素

    文档出处:redisdoc.com/list/rpoplpush.html模式: 安全的队列 Redis的列表经常被用作队列(queue),用于在不同程序之间有序地交换消息(message).一个客户 ...

  2. springboot静态资源路径制定

    spring.resources.static-location参数指定了Spring Boot-web项目中静态文件存放地址, 该参数默认设置为: classpath:/static, classp ...

  3. MPI集群搭建

    高性能计算     ubantu下集群搭建 参考博客:https://blog.csdn.net/u012304016/article/details/52423738(尊重别人的知识产权),一些细节 ...

  4. Python之requests模块-session

    http协议本身是无状态的,为了让请求之间保持状态,有了session和cookie机制.requests也提供了相应的方法去操纵它们. requests中的session对象能够让我们跨http请求 ...

  5. 安装redis 6.0.6

    1.规划目录:下载目录.安装目录.redis数据目录mkdir -p /data/appmkdir -p /opt/redis_cluster/redis_6379/{conf,logs,pid}mk ...

  6. [考试总结]noip模拟44

    这个真的是一个 \(nb\) 题. 考试快要结束的时候,在机房中只能听到此起彼伏的撕吼. 啊---------- 然后人们预测这自己的得分. \(\color{red}{\huge{0}}\) \(\ ...

  7. freemodbus移植、实例及其测试方法

    Modbus简介 参考:Modbus​协议​深入​讲解 https://www.ni.com/zh-cn/innovations/white-papers/14/the-modbus-protocol ...

  8. QT之静态函数发送信号

    一.简介 由于博主本人是初学者对QT的机制不了解,所以遇到了一个比较大的坑,特此记录一下.我遇到的问题是无法在静态函数中向另外一个类发送信号.解决办法:先将信号发送给同类中的普通函数,然后在从普通函数 ...

  9. 珠峰2016,第9期 vue.js 笔记部份

    在珠峰参加培训好年了,笔记原是记在本子上,现在也经不需要看了,搬家不想带上书和本了,所以把笔记整理下,存在博客中,也顺便复习一下 安装vue.js 因为方便打包和环境依赖,所以建意npm  init  ...

  10. kubectl apply部署时可以用 --record 方便记录版本 和回退

    1.部署时正常时下面的 kubectl apply -f http.yaml 2.如果修改文件文件重新部署或者之前有上一个版本的  想回退上一个的 可以无感知的回退回去 不影响业务 其中http-de ...