题解 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}\)。
正解
正解的做法就比较神仙了,算法方面就是线段树合并+动态开点+树上差分,在加上一点虚树的思想。
对于每个点建一棵线段树,然后再树上差分线段树合并就是各个节点对于答案的贡献了,因此,现在的问题在于对每个节点的处理。
不难发现有以下性质:
对于所有可以与 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]语言的更多相关文章
- 【题解】Luogu P5327 [ZJOI2019]语言
原题传送门 看到这种树上统计点对个数的题一般是线段树合并,这题也不出意外 先对这棵树进行树剖,对于每次普及语言,在\(x,y\)两点的线段树上的\(x,y\)两位置打\(+1\)标记,在点\(fa[l ...
- P5327 [ZJOI2019]语言
一边写草稿一边做题吧.要看题解的往下翻,或者是旁边的导航跳一下. 草稿 因为可以开展贸易活动的条件是存在一种通用语 \(L\) 满足 \(u_i\) 到 \(v_i\) 的最短路径上都会 \(L\) ...
- Luogu P5327 [ZJOI2019]语言
ZJOI2019Day2的温暖题,然后考场上只会大常数的\(O(n\log^3 n)\),就懒得写拿了60pts走人 首先我们简化题意,容易发现每个点能到达的点形成了一个联通块,我们只需要统计出这个联 ...
- 题解 [ZJOI2019]语言
题目传送门 题目大意 给出一个 \(n\) 个点的树,现在有 \(m\) 次操作,每次可以选择一个链 \(s,t\),,然后这条链上每个点都会增加一个相同属性,问对于每一个点有与它相同属性的有多少个点 ...
- [ZJOI2019]语言
树链剖分入门题吧 一个非常直观的想法是使用树剖将一条链拆成\(log^2n\)个矩形,套用矩形面积并算法即可得到一个垃圾的3个log过不去算法 为了得到一个两个log的做法,我们观察一下拆出来的矩形的 ...
- [ZJOI2019]语言[树链的并、线段树合并]
题意 题目链接 分析 考虑枚举每个点的答案,最后除以 2 即可. 可以与 \(u\) 构成合法点对 的集合 为所有经过了 \(u\) 的链的并.因为这些链两两有交,根据结论 "树上两条相交的 ...
- [Luogu5327][ZJOI2019]语言(树上差分+线段树合并)
首先可以想到对每个点统计出所有经过它的链的并所包含的点数,然后可以直接得到答案.根据实现不同有下面几种方法.三个log:假如对每个点都存下经过它的链并S[x],那么每新加一条路径进来的时候,相当于在路 ...
- Luogu5327 ZJOI2019语言(树上差分+线段树合并)
暴力树剖做法显然,即使做到两个log也不那么优美. 考虑避免树剖做到一个log.那么容易想到树上差分,也即要对每个点统计所有经过他的路径产生的总贡献(显然就是所有这些路径端点所构成的斯坦纳树大小),并 ...
- [ZJOI2019]语言——树剖+树上差分+线段树合并
原题链接戳这儿 SOLUTION 考虑一种非常\(naive\)的统计方法,就是对于每一个点\(u\),我们维护它能到达的点集\(S_u\),最后答案就是\(\frac{\sum\limits_{i= ...
随机推荐
- WIKI和JIRA-安装与使用
1.Wiki介绍1.1 Wiki(多人协作的写作系统)是一种超文本系统,这种超文本系统支持面向社群的协作式写作,即人人可编辑.在公司的项目管理中,可以把它当作文档管理和信息组织(Portlet)系统来 ...
- 【转载】kvm迁移
https://www.jianshu.com/p/60132085a3c9 kvm分静态和动态迁移,静态就是关机迁移,比较简单,动态迁移就是不关闭服务器进行迁移.静态迁移:确定虚拟机关闭 https ...
- 搭建LAMP环境部署Ecshop电商网站
实战-部署Ecshop电商网站 实验环境 Centos7 ip:192.168.121.17 一.关闭防火墙和selinux [root@localhost ~]# systemctl stop fi ...
- Rust 多态
Rust 多态 分发 多态的上下文中的方法解析过程被称为分发,调用该方法称为分发化,在支持多态的主流语言中,分发可以通过以下任意一种方式进行. 静态分发 当在编译期决定要调用的方法时,它被称为静态分发 ...
- Scala 字符串插值器
Scala 提供了三种创新的字符串插值方法:s,f和raw,使用他们我们可以方便快捷的组合字符串. s 字符串插值器 在任何字符串前加上s,就可以直接在串中使用变量了,在生成字符串的时候会隐式调用其t ...
- Python3冒泡排序
练习:将路径为 D:\data.txt 的文件读取,并取出数字部分进行排序(不能使用内置排序方法),这里我们使用冒泡排序法 文件读取并取出数字部分(略) 一:什么叫冒泡排序 冒泡排序(Bubble S ...
- 修改mysql中数据库存储主路径
一.首先把mysql的服务先停掉. 二.更改MySQL配置文件My.ini中的数据库存储主路径 打开文件夹C:\ProgramData\MySQL\MySQL Server 5.7中的my.ini文件 ...
- 痞子衡嵌入式:在SBL项目实战中妙用i.MXRT1xxx里SystemReset不复位的GPR寄存器
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT1xxx里SystemReset不复位的GPR寄存器的小妙用. 我们知道稍大规模的项目代码设计一般都是多人协作完成的,在项目 ...
- 工作流Activiti框架中表单的使用!详细解析内置表单和外置表单的渲染
Activiti中的表单 Activiti提供了一种方便而且灵活的方式在业务流程中以手工方式添加表单 对表单的支持有2种方式: 通过表单属性对内置表单进行渲染 通过表单属性对外置表单进行渲染 表单属性 ...
- 工作流中的数据持久化详解!Activiti框架中JPA的使用分析
Activiti中JPA简介 可以使用JPA实体作为流程变量, 并进行操作: 基于流程变量更新已有的JPA实体,可以在用户任务的表单中填写或者由服务任务生成 重用已有的领域模型,不需要编写显示的服务获 ...