P5327 [ZJOI2019]语言

解题思路

暴力

首先讲一下我垃圾的 40pts 的暴力(其他 dalao 都是 60pts 起步):

当然评测机快的话(比如 LOJ 的),可以卡过 3,4 个点(逃。

对于 1,2 测试点的话,我们直接记录两个节点之间路径上的所有点,然后用一个二维数组存一下两个点是否能互相贸易。

最后暴力求 ans 就好了。。

然后我们看到了链的部分分,然后就是在序列上的处理了:

对于每一个操作,我们记录下左右端点,然后按照左端点为第一关键字,右端点为第二关键字进行排序。

把各个操作分成若干组,保证每一组的最左端的点比前一组的所有的右端点都要大。

然后对于不同组的第一个直接给答案加上\(C_{r-l+1}^2\),也就是\(\dfrac{(r-l)\times(r-l+1)}{2}\)。

对于同一组的,如果该区间在本组此前的区间内,那么它就没有贡献。

否则把它在组内的长度乘上在组外的长度还有组外边长度的自由组合。

设此时这一组的右端点是 maxr,贡献就是\((r-maxr)\times(maxr-l)+\dfrac{(r-maxr)\times(r-maxr+1)}{2}\)。

\(code\)

正解

正解的做法就比较神仙了,算法方面就是线段树合并+动态开点+树上差分,在加上一点虚树的思想。

对于每个点建一棵线段树,然后再树上差分线段树合并就是各个节点对于答案的贡献了,因此,现在的问题在于对每个节点的处理。

不难发现有以下性质:

对于所有可以与 x 贸易的点实际上就构成了一个生成树,也可以叫做联通块。

如果点 s 和 t 的路径会经过点 x ,那么我们称 s 和 t 为 x 的两个极远点,那么就有了以下结论:

x 的生成树大小其实就是能把 x 的所有极远点的最小生成树。

为了方便,我们硬点存在极远点 1 ,最后再除去 1 的贡献。

然后,如果我们现在需要把 y 点加入到 x 的生成树里,其实就是需要把该生成树里距离 y 最近的点与 y 连起来。

假设那个点是 z ,那么 y 的贡献就是\(dep_y-dep_{\operatorname{lca}(y,z)}\)。

对于 1 点的贡献其实就是所有点的 lca 的 dep 之和。

在线段树上进行操作时,存储四个值:操作数,两个极远点,贡献

接下来就是转移了,因为线段树是建立在时间戳上的,所以对于一个区间来说,两个极远点一定分别来自左右两个子区间。

对于两个子区间在向上更新时不能单纯的只记为两个区间的加和,贡献还应该算上两个子区间之间最近的节点连接起来的贡献,也就是这两个节点的 lca 的 dep 值。

最后就是实现细节了:注意下时间戳和标号之间的转化就好了。

对于求 lca 的时候可以用 \(\mathcal{O}(1)\)查询的优秀 RMQ-ST 算法 但是貌似,树链剖分的 \(\mathcal{O}(n)\)预处理更快一些,其实都大同小异了。。

code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10,M=N<<1;
int n,m,tot,ans,root[N];
int tim,dfn[N],id[N],siz[N],son[N],dep[N],topp[N],fa[N];
vector<int> del[N];
struct Edge
{
int tot,head[N],nxt[M],ver[M];
void add(int x,int y)
{
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
}e;
struct Vector_Tree
{
int l,r,s,t,dat,f;
}tre[N*80];
void pre_dfs(int x)
{
siz[x]=1;
for(int i=e.head[x];i;i=e.nxt[i])
{
int to=e.ver[i];
if(to==fa[x])
continue;
fa[to]=x;
dep[to]=dep[x]+1;
pre_dfs(to);
siz[x]+=siz[to];
if(siz[to]>siz[son[x]])
son[x]=to;
}
}
void pre_dfs(int x,int tp)
{
topp[x]=tp;
dfn[x]=++tim;
id[tim]=x;
if(son[x])
pre_dfs(son[x],tp);
for(int i=e.head[x];i;i=e.nxt[i])
if(!dfn[e.ver[i]])
pre_dfs(e.ver[i],e.ver[i]);
}
int LCA(int x,int y)
{
if(!x||!y) return 0;
while(topp[x]^topp[y])
{
if(dep[topp[x]]<dep[topp[y]])
swap(x,y);
x=fa[topp[x]];
}
if(dep[x]>dep[y])
swap(x,y);
return x;
}
void push_up(int x)
{
tre[x].s=(tre[tre[x].l].s?tre[tre[x].l].s:tre[tre[x].r].s);
tre[x].t=(tre[tre[x].r].t?tre[tre[x].r].t:tre[tre[x].l].t);
if(!tre[tre[x].l].t||!tre[tre[x].r].s) tre[x].f=tre[tre[x].l].f+tre[tre[x].r].f;
else tre[x].f=tre[tre[x].l].f+tre[tre[x].r].f-dep[LCA(tre[tre[x].l].t,tre[tre[x].r].s)];
}
void insert(int &x,int l,int r,int pos,int num)
{
if(!x) x=++tot;
if(l==r)
{
tre[x].dat+=num;
tre[x].s=tre[x].t=(tre[x].dat?id[pos]:0);
tre[x].f=(tre[x].dat?dep[id[pos]]:0);
return ;
}
int mid=(l+r)>>1;
if(pos<=mid) insert(tre[x].l,l,mid,pos,num);
else insert(tre[x].r,mid+1,r,pos,num);
push_up(x);
}
void merge(int &x,int y,int l,int r)
{
if(!x||!y)
{
x|=y;
return ;
}
if(l==r)
{
tre[x].dat+=tre[y].dat;
tre[x].f=(tre[x].f?tre[x].f:tre[y].f);
tre[x].s=(tre[x].s?tre[x].s:tre[y].s);
tre[x].t=(tre[x].t?tre[x].t:tre[y].t);
return ;
}
int mid=(l+r)>>1;
merge(tre[x].l,tre[y].l,l,mid);
merge(tre[x].r,tre[y].r,mid+1,r);
push_up(x);
}
int query(int x)
{
return tre[x].f-dep[LCA(tre[x].s,tre[x].t)];
}
void redfs(int x)
{
for(int i=e.head[x];i;i=e.nxt[i])
if(e.ver[i]!=fa[x])
redfs(e.ver[i]);
for(int i=0;i<del[x].size();i++)
insert(root[x],1,tim,dfn[del[x][i]],-1);
ans+=query(root[x]);
merge(root[fa[x]],root[x],1,tim);
}
#undef int
int main()
{
#define int register long long
#define ll long long
scanf("%lld%lld",&n,&m);
for(int i=1,x,y;i<n;i++)
{
scanf("%lld%lld",&x,&y);
e.add(x,y);
e.add(y,x);
}
pre_dfs(1);
pre_dfs(1,1);
for(int i=1,x,y;i<=m;i++)
{
scanf("%lld%lld",&x,&y);
insert(root[x],1,tim,dfn[x],1);
insert(root[x],1,tim,dfn[y],1);
insert(root[y],1,tim,dfn[x],1);
insert(root[y],1,tim,dfn[y],1);
int lca=LCA(x,y);
del[lca].push_back(x);
del[lca].push_back(y);
del[fa[lca]].push_back(x);
del[fa[lca]].push_back(y);
}
redfs(1);
printf("%lld\n",ans/2ll);
return 0;
}

题解 P5327 [ZJOI2019]语言的更多相关文章

  1. 【题解】Luogu P5327 [ZJOI2019]语言

    原题传送门 看到这种树上统计点对个数的题一般是线段树合并,这题也不出意外 先对这棵树进行树剖,对于每次普及语言,在\(x,y\)两点的线段树上的\(x,y\)两位置打\(+1\)标记,在点\(fa[l ...

  2. P5327 [ZJOI2019]语言

    一边写草稿一边做题吧.要看题解的往下翻,或者是旁边的导航跳一下. 草稿 因为可以开展贸易活动的条件是存在一种通用语 \(L\) 满足 \(u_i\) 到 \(v_i\) 的最短路径上都会 \(L\) ...

  3. Luogu P5327 [ZJOI2019]语言

    ZJOI2019Day2的温暖题,然后考场上只会大常数的\(O(n\log^3 n)\),就懒得写拿了60pts走人 首先我们简化题意,容易发现每个点能到达的点形成了一个联通块,我们只需要统计出这个联 ...

  4. 题解 [ZJOI2019]语言

    题目传送门 题目大意 给出一个 \(n\) 个点的树,现在有 \(m\) 次操作,每次可以选择一个链 \(s,t\),,然后这条链上每个点都会增加一个相同属性,问对于每一个点有与它相同属性的有多少个点 ...

  5. [ZJOI2019]语言

    树链剖分入门题吧 一个非常直观的想法是使用树剖将一条链拆成\(log^2n\)个矩形,套用矩形面积并算法即可得到一个垃圾的3个log过不去算法 为了得到一个两个log的做法,我们观察一下拆出来的矩形的 ...

  6. [ZJOI2019]语言[树链的并、线段树合并]

    题意 题目链接 分析 考虑枚举每个点的答案,最后除以 2 即可. 可以与 \(u\) 构成合法点对 的集合 为所有经过了 \(u\) 的链的并.因为这些链两两有交,根据结论 "树上两条相交的 ...

  7. [Luogu5327][ZJOI2019]语言(树上差分+线段树合并)

    首先可以想到对每个点统计出所有经过它的链的并所包含的点数,然后可以直接得到答案.根据实现不同有下面几种方法.三个log:假如对每个点都存下经过它的链并S[x],那么每新加一条路径进来的时候,相当于在路 ...

  8. Luogu5327 ZJOI2019语言(树上差分+线段树合并)

    暴力树剖做法显然,即使做到两个log也不那么优美. 考虑避免树剖做到一个log.那么容易想到树上差分,也即要对每个点统计所有经过他的路径产生的总贡献(显然就是所有这些路径端点所构成的斯坦纳树大小),并 ...

  9. [ZJOI2019]语言——树剖+树上差分+线段树合并

    原题链接戳这儿 SOLUTION 考虑一种非常\(naive\)的统计方法,就是对于每一个点\(u\),我们维护它能到达的点集\(S_u\),最后答案就是\(\frac{\sum\limits_{i= ...

随机推荐

  1. 强哥JavaScript学习笔记

    js文件放header头最后,js代码放body体最后 js语言定位: js是基于对象的语言 php.java是面向对象的语言 定义变量: var str="hello world" ...

  2. Linux 部署 iSCSI 客户端配置(Linux)

    Linux 部署 iSCSI 客户端配置(Linux) 客户端环境 Client :RHEL8 IP : 192.168.121.11 一.测试与服务端的连通性 [root@Client-linux ...

  3. 037.Python的UDP语法

    UDP语法 1 创建一个socket的UDP对象 import socket #创建对象 socket.SOCK_DGRAM 代表UDP协议 sk = socket.socket(type=socke ...

  4. Centos6.9以下查看端口占用情况和开启端口命令

    Centos查看端口占用情况命令,比如查看80端口占用情况使用如下命令:   lsof -i tcp:80   列出所有端口   netstat -ntlp   1.开启端口(以80端口为例)     ...

  5. Git 系列教程(14)- 远程分支

    远程分支 远程引用是对远程仓库的引用(指针),包括分支.标签等等 你可以通过 git ls-remote <remote> 来显式地获得远程引用的完整列表 polo@B-J5D1MD6R- ...

  6. SpringBoot基础学习(二) SpringBoot全局配置文件及配置文件属性值注入

    全局配置文件 全局配置文件能够对一些默认配置值进行修改.SpringBoot 使用一个名为 application.properties 或者 application.yaml的文件作为全局配置文件, ...

  7. 【进阶之路】多线程条件下分段处理List集合的几种方法

    这两个月来因为工作和家庭的事情,导致一直都很忙,没有多少时间去汲取养分,也就没有什么产出,最近稍微轻松了一点,后续的[进阶之路]会慢慢回到正轨. 开门见山的说,第一次接触到多线程处理同一个任务,是使用 ...

  8. 学习Git的一些总结

    Git是以后公司工作必不可少的,所以早点了解使用它是很有必要的 一般国外的开源是GitHub 国内的是码云Gitee 至于git的安装教程,这里就不啰嗦啦,面向百度即可,安装完成鼠标右键会多几个选项: ...

  9. 五种开源API网关实现组件对比

    五种开源API网关实现组件对比     API 网关一些实现 使用一个组件时,尤其是这种比较流行的架构,组件肯定存在开源的,我们不必自己去从零开始去实现一个网关,自己开发一个网关的工作量是相当可观的, ...

  10. 调试动态加载的js

    用浏览器无法调试异步加载页面里包含的js文件.简单的说就是在调试工具里面看不到异步加载页面里包含的js文件   最近在一个新的web项目中开发功能.这个项目的管理界面有一个特点,框架是固定的,不会刷新 ...