膜拜一发\(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. Go并发控制--WaitGroup篇

    目录 1. 前言 2. 使用WaitGroup控制 2.1 使用场景 2.2 信号量 1.3 WaitGroup 数据结构 2.3.1 Add () 方法 2.3.2 Wait() 2.3.3 Don ...

  2. Win10 pip install augimg 报 OSError: [WinError 126] 找不到指定的模块,解决办法

    第一种Win10下python成功安装augimg的方法: 下载Shapely,地址https://www.lfd.uci.edu/~gohlke/pythonlibs/#shapely,选择对应版本 ...

  3. idea控制台中文乱码解决办法

    也可以通过idea右下角的设置,但是properties文件是不能设置的,这个只能在file->setting->file encodings 设置

  4. 一种封装Retrofit的方法,可以自动解析Gson,回避Method return type must not include a type variable or wildcard: retrofit2.Call<T>的问题

    封装目的:屏蔽底层实现,提供统一接口,并支持Gson自动转化 最初封装: //请求方法 interface RequestListener { interface PostListener { @PO ...

  5. JavaScript——字符串——模板字符串

    JavaScript--字符串--模板字符串 字符串可以用反引号包裹起来,其中的${expression}表示特殊的含义,JavaScript会将expression代表的变量的值和反引号中的其它普通 ...

  6. 远程线程注入DLL

    远程线程注入 0x00 前言 远程线程注入是一种经典的DLL注入技术.其实就是指一个新进程中另一个进程中创建线程的技术. 0x01 介绍 1.远程线程注入原理 画了一个图大致理解了下远程线程注入dll ...

  7. RabbitMQ-TTL-死信队列_DLX

    1. 简介 死信队列,简称:DLX,Dead Letter Exchange(死信交换机),当消息成为Dead message后,可以被重新发送到另外一个交换机,这个交换机就是DLX. (一般会将DL ...

  8. type switch使用

    type    switchs用法 这里存在一个未知类型变量的内省操作(introspection operation),就是x.(type),其中x是interface{}类型

  9. wpf 多表头

    WPF多表头技术探索总结 方案一:Grid+TextBlock嵌套DataGrid方式. 该方案是现在项目中已使用的方案.实现起来比较简单,但不具有通用性,不同数据DataGrid需要指定不同的Tex ...

  10. mybatis整理笔记

    以下是idea2018辑编器 新建 Maven工程 1  file ->new ->project 新建后编程器在右下角加载插件.,这个时候需要会儿,  加载好后,软件目录会多一个ja包 ...