【XSY3350】svisor - 点分治+虚树dp
题目来源:NOI2019模拟测试赛(九)
题意:
吐槽:
第一眼看到题觉得这不是震波的完全弱化版吗……然后开开心心的码了个点分治
码到一半突然发现看错题了……心态崩了于是就弃疗手玩提答去了
于是就快乐垫底了
最后发现这是个最毒瘤的题……改题写+调了一天,代码长度再次进入前五排行榜
题解:
(明明是在线做法为什么不强制在线呢)
由于是询问树上某些关键点的信息,且$\sum k$比较小,所以考虑建出虚树处理询问;
如图,对于虚树上一个不是关键点的点$u$,显然他的最大监视半径就是$max\{r_v-dis_{u,v}|v是u的子节点\}$;
这个可以通过逆拓扑序在虚树上一遍DP求出来;
由于虚树的点数是$O(k)$的,所以可以直接用点分治预处理离每个点距离小于等于$r$的点数量,然后$O(logn)$处理虚树上所有点的询问;
但是这样做显然会有点被重复计算,如图,阴影部分的点就被计算了两次(图中是一条链,实际上可能还有其它分支也被重复计算了);
考虑被重复计算的部分有什么性质,容易发现它实际上就是距离$u$和$v$的监视半径重叠部分中心不超过$\frac{r_u+r_v-dis_{u,v}}{2}$的点集,显然这也可以当成类似的询问用点分治处理;
对虚树上每一对有重叠的父子都类似处理一遍,就可以减去所有重叠部分的额外影响,因此不用额外考虑被覆盖了三次四次甚至以上的点;
由于虚树的边数=点数-1,所以这一部分的时间复杂度显然是对的;
但是这样还有一个小问题:如图,如果重叠部分边长度为奇数,那么是找不到中心点的;
实际上这时中心点在一条边上,所以可以拆边,把树上每条原本的边都看成一个点,就可以解决了;
至此这道题终于做完了……具体实现的时候并不用把虚树真正建出来,只记录每个点的父节点和拓扑序即可;
总的时间复杂度$O((n+\sum k)logn)$,常数很大。
写的时候细节超多……外面的点分治要记一万个信息……轻松喜提200行+
代码:
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<stack>
#define inf 2147483647
#define eps 1e-9
using namespace std;
typedef long long ll;
typedef double db;
struct edge{
int v,next;
}a[];
int n,m,u,v,K,S,rt,mxd,ans,tot=,tim=,fkfa[],dfn[],nmd[],head[],dps[],md1[],*s1[],md2[],*s2[],ddp[],dfdep[],dfrt[][],dfds[][],k[],r[],siz[],mx[],dep[],fa[][];
bool used[],isk[];
stack<int>st;
vector<int>vec;
bool cmp(int a,int b){
return dfn[a]<dfn[b];
}
void add(int u,int v){
a[++tot].v=v;
a[tot].next=head[u];
head[u]=tot;
}
void dfs(int u,int ff,int dpt){
dep[u]=dpt;
dfn[u]=++tim;
nmd[tim]=u;
fa[u][]=ff;
for(int i=;i<=;i++)fa[u][i]=fa[fa[u][i-]][i-];
for(int tmp=head[u];tmp!=-;tmp=a[tmp].next){
int v=a[tmp].v;
if(v!=ff){
dfs(v,u,dpt+);
}
}
}
int lca(int u,int v){
if(dep[u]<dep[v])swap(u,v);
int l=dep[u]-dep[v];
for(int i=;i>=;i--){
if((<<i)&l){
u=fa[u][i];
}
}
if(u==v)return u;
for(int i=;i>=;i--){
if(fa[u][i]!=fa[v][i]){
u=fa[u][i],v=fa[v][i];
}
}
return fa[u][];
}
int getfa(int u,int l){
for(int i=;i>=;i--){
if((<<i)&l){
u=fa[u][i];
}
}
return u;
}
void dfsrt(int u,int fa){
siz[u]=;
mx[u]=;
mxd=max(mxd,ddp[u]);
if(u<=n)dps[ddp[u]]++;
for(int tmp=head[u];tmp!=-;tmp=a[tmp].next){
int v=a[tmp].v;
if(!used[v]&&v!=fa){
dfsrt(v,u);
siz[u]+=siz[v];
mx[u]=max(mx[u],siz[v]);
}
}
mx[u]=max(mx[u],S-siz[u]);
if(mx[u]<mx[rt])rt=u;
}
void dfsdep(int u,int fa,int ls,int dpt){
siz[u]=;
ddp[u]=dpt;
dfdep[u]++;
dfrt[u][dfdep[u]]=ls;
dfds[u][dfdep[u]]=dpt;
mxd=max(mxd,dpt);
if(u<=n)dps[dpt]++;
for(int tmp=head[u];tmp!=-;tmp=a[tmp].next){
int v=a[tmp].v;
if(!used[v]&&v!=fa){
dfsdep(v,u,ls,dpt+);
siz[u]+=siz[v];
}
}
}
void divide(int u){
used[u]=true;
ddp[u]=mxd=;
dfsdep(u,,u,);
md1[u]=mxd;
s1[u]=new int[mxd+];
for(int i=;i<=mxd;i++){
s1[u][i]=dps[i];
if(i)s1[u][i]+=s1[u][i-];
dps[i]=;
}
for(int tmp=head[u];tmp!=-;tmp=a[tmp].next){
int v=a[tmp].v;
if(!used[v]){
mxd=rt=;
S=siz[v];
dfsrt(v,u);
md2[rt]=mxd;
s2[rt]=new int[mxd+];
for(int i=;i<=mxd;i++){
s2[rt][i]=dps[i];
if(i)s2[rt][i]+=s2[rt][i-];
dps[i]=;
}
divide(rt);
}
}
}
void buildfaketree(){
while(!st.empty())st.pop();
vec.clear();
sort(k+,k+K+,cmp);
st.push(k[]);
for(int i=;i<=K;i++){
int z=lca(k[i],st.top());
if(!isk[z])r[z]=-;
while(!st.empty()&&dep[z]<dep[st.top()]){
int x=st.top();
st.pop();
vec.push_back(x);
if(!st.empty()&&dep[z]<dep[st.top()])fkfa[x]=st.top();
else fkfa[x]=z;
}
if(st.empty()||dep[z]>dep[st.top()])st.push(z);
if(k[i]!=z)st.push(k[i]);
}
while(!st.empty()){
int x=st.top();
st.pop();
vec.push_back(x);
if(!st.empty())fkfa[x]=st.top();
else fkfa[x]=;
}
}
int getci(int u,int r){
int nw,d1,d2,ret=;
for(int i=dfdep[u];i;i--){
nw=dfrt[u][i];
d1=dfds[u][i];
d2=dfds[u][i-];
if(d1<=r)ret+=s1[nw][min(r-d1,md1[nw])];
if(i>&&d2<=r)ret-=s2[nw][min(r-d2,md2[nw])];
}
return ret;
}
void getans(){
ans=;
int u,ft,ds,mid,len=vec.size();
for(int i=;i<len;i++){
u=vec[i];
r[fkfa[u]]=max(r[fkfa[u]],r[u]-dep[u]+dep[fkfa[u]]);
}
for(int i=len-;i>=;i--){
u=vec[i];
r[u]=max(r[u],r[fkfa[u]]-dep[u]+dep[fkfa[u]]);
}
for(int i=;i<len;i++){
u=vec[i];
ans+=getci(u,r[u]);
}
for(int i=;i<len-;i++){
u=vec[i];
ft=fkfa[u];
ds=dep[u]-dep[ft];
if(r[u]+r[ft]>=ds){
mid=getfa(u,(r[u]-r[ft]+ds)/);
ans-=getci(mid,r[u]-(r[u]-r[ft]+ds)/);
}
}
}
void pt(int *s){
for(int i=;i<=n*-;i++)printf("%d ",s[i]);
puts("");
}
int main(){
memset(head,-,sizeof(head));
scanf("%d",&n);
for(int i=;i<n;i++){
scanf("%d%d",&u,&v);
add(u,n+i);
add(n+i,u);
add(v,n+i);
add(n+i,v);
}
dfs(,,);
S=n*-;
mx[rt]=;
dfsrt(,-);
memset(dps,,sizeof(dps));
divide(rt);
scanf("%d",&m);
while(m--){
scanf("%d",&K);
r[]=-;
for(int i=;i<=K;i++){
scanf("%d",&k[i]);
scanf("%d",&r[k[i]]);
r[k[i]]*=;
isk[k[i]]=true;
}
buildfaketree();
for(int i=;i<=K;i++)isk[k[i]]=false;
getans();
printf("%d\n",ans);
}
return ;
}
【XSY3350】svisor - 点分治+虚树dp的更多相关文章
- 【UOJ347】【WC2018】通道 边分治 虚树 DP
题目大意 给你三棵树,点数都是\(n\).求 \[ \max_{i,j}d_1(i,j)+d_2(i,j)+d_3(i,j) \] 其中\(d_k(i,j)\)是在第\(k\)棵数中\(i,j\)两点 ...
- LOJ 2339 「WC2018」通道——边分治+虚树
题目:https://loj.ac/problem/2339 两棵树的话,可以用 CTSC2018 暴力写挂的方法,边分治+虚树.O(nlogn). 考虑怎么在这个方法上再加一棵树.发现很难弄. 看了 ...
- bzoj 3572世界树 虚树+dp
题目大意: 给一棵树,每次给出一些关键点,对于树上每个点,被离它最近的关键点(距离相同被标号最小的)控制 求每个关键点控制多少个点 分析: 虚树+dp dp过程如下: 第一次dp,递归求出每个点子树中 ...
- bzoj 2286 [Sdoi2011]消耗战 虚树+dp
题目大意:多次给出关键点,求切断边使所有关键点与1断开的最小费用 分析:每次造出虚树,dp[i]表示将i和i子树与父亲断开费用 对于父亲x,儿子y ①y为关键点:\(dp[x]\)+=\(dismn( ...
- 【BZOJ】2286: [Sdoi2011]消耗战 虚树+DP
[题意]给定n个点的带边权树,每次询问给定ki个特殊点,求隔离点1和特殊点的最小代价.n<=250000,Σki<=500000. [算法]虚树+DP [题解]考虑普通树上的dp,设f[x ...
- [BZOJ5287][HNOI2018]毒瘤(虚树DP)
暴力枚举非树边取值做DP可得75. 注意到每次枚举出一个容斥状态的时候,都要做大量重复操作. 建立虚树,预处理出虚树上两点间的转移系数.也可动态DP解决. 树上倍增.动态DP.虚树DP似乎是这种问题的 ...
- [BZOJ2286][SDOI2011]消耗战(虚树DP)
2286: [Sdoi2011]消耗战 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 4998 Solved: 1867[Submit][Statu ...
- BZOJ 3572 [HNOI2014]世界树 (虚树+DP)
题面:BZOJ传送门 洛谷传送门 题目大意:略 细节贼多的虚树$DP$ 先考虑只有一次询问的情况 一个节点$x$可能被它子树内的一个到x距离最小的特殊点管辖,还可能被管辖fa[x]的特殊点管辖 跑两次 ...
- 洛谷P2495 [SDOI2011]消耗战(虚树dp)
P2495 [SDOI2011]消耗战 题目链接 题解: 虚树\(dp\)入门题吧.虚树的核心思想其实就是每次只保留关键点,因为关键点的dfs序的相对大小顺序和原来的树中结点dfs序的相对大小顺序都是 ...
随机推荐
- C#--excel操作控件--interop是什么
这个是微软出的操作excel的类库,为什么这个比NPOI的下载量少这么多?
- @Zookeeper可视化工具。 ZK 安装 node-zk-browser。2015.10.22亲测可用
zookeeper基本是基于API和console进行znode的操作,并没有一个比较方便的操作界面,这里也发现了taobao 伯岩写的一个工具,可以比较方便的查询zookeeper信息. 工具的开发 ...
- [ajax 学习笔记] ajax初试
ajax全称是:asynchronous javasctipt and xml. 1.为什么须要ajax? 一般web程序与server的交互是:页面发送请求等待server处理,server处理数据 ...
- Linux 管道是什么 ?原理
简单点就是说,一个命令的结果作为另外一个命令(结果)的输入 . 管道是linux提供的一种常见的进程通信工具,也是很多shell命令能够灵活组合产生强大用途的一个重要工具. 管道是什么? 管道,顾名思 ...
- luogu3865 【模板】 ST表
题目大意:给出一段序列,每次查询一段区间,求区间最大值. ST表:设原序列为A,定义F[i][k]为A[i][2k-1]的最大值.有递归式:F[i][k]=max(F[i][k-1], F[i+2k- ...
- 0x56 状压DP
gan这两题怎么差不多 #include<cstdio> #include<iostream> #include<cstring> #include<cstd ...
- 2017-3-9 leetcode 283 287 289
今天操作系统课,没能安心睡懒觉23333,妹抖龙更新,可惜感觉水分不少....怀念追RE0的感觉 =================================================== ...
- 6.11Realm简介
CasRealm 统一认证授权中心 跟单点登录有关的.IniRealm 静态文件
- js设计模式-桥接模式
桥接模式定义:桥梁模式的用意是"将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化".这句话有三个关键词,也就是抽象化.实现化和 ...
- golang互斥锁和读写锁
一.互斥锁 互斥锁是传统的并发程序对共享资源进行访问控制的主要手段.它由标准库代码包sync中的Mutex结构体类型代表.sync.Mutex类型(确切地说,是*sync.Mutex类型)只有两个公开 ...