「ZJOI2019」语言 解题报告
「ZJOI2019」语言
3个\(\log\)做法比较简单,但是写起来还是有点麻烦的。
大概就是树剖把链划分为\(\log\)段,然后任意两段可以组成一个矩形,就是个矩形面积并,听说卡卡就过去了。
好像这个可以被优化到两个\(\log\),算了,估计挺麻烦的。
一个\(\log\)的做法看起来还挺厉害的。
考虑钦定某个点算它的贡献,于是我们要算的是所有经过它的链的并的大小。
但是染色这个东西看起来就很不可搞,我们可以挖掘一下这个并的简单性质。
注意到,这个并是联通的,可以看做是一个生成子树,然后我们需要求这个子树的大小。
考虑一个类似建虚树的过程,我们把\(n\)个链拆成\(2n-1\)条链,即按\(dfs\)序进行排序,相邻两点构成链,我们按\(dfs\)序新加入一个点\(x\)时,新的链的贡献就是\(dep_x-dep_{lca(x,y)}\),\(y\)是上一个加入的点,这个可以画图体会一下。
但是一个一个加还是不太好维护,考虑我们可不可以合并两个子树的集合,显然,贡献和左边的最右端\(x\)以及右边的最左端\(y\)有关
然后我们发现需要讨论\(x,y\)是否在同一条链上,不在就很简单,两棵树没有交,随便算算就可以了。
如果在的话,我们发现有一部分是重叠的,算出这部分重叠可能需要求出左边的最浅点,为了方便,我们钦定每个集合的最浅点都是根(并加入根),然后在合并的时候可以发现,只需要减去\(dep_{lca(x,y)}\)就可以了,这对上面不在同一条链上仍然是适用的。
在合并到最后为了去掉根的贡献,我们维护一个点集的\(lca\)的深度,最后减去它就可以了。
这样的话,我们就可以用线段树维护这个子树的大小,并支持单点修改与查询。
对于链的修改,我们可以打差分tag,在相应的节点的线段树中加入与删除链的两个端点\(s,t\)。
可以发现,为了继承儿子的信息,还需要一个线段树合并。
Code:
#include <cstdio>
#include <cctype>
#include <algorithm>
#include <vector>
#define ll long long
const int SIZE=1<<21;
char ibuf[SIZE],*iS,*iT;
#define gc() (iS==iT?(iT=(iS=ibuf)+fread(ibuf,1,SIZE,stdin),iS==iT?EOF:*iS++):*iS++)
template <class T>
void read(T &x)
{
x=0;char c=gc();
while(!isdigit(c)) c=gc();
while(isdigit(c)) x=x*10+c-'0',c=gc();
}
const int N=1e5+10;
int head[N],to[N<<1],Next[N<<1],cnt;
void add(int u,int v)
{
to[++cnt]=v,Next[cnt]=head[u],head[u]=cnt;
}
int n,m;
ll ans;
int dfn[N],ha[N],st[N<<1][20],Log[N<<1],fir[N],dep[N],par[N],dfsclock,dcnt;
void dfs(int now,int fa)
{
dfn[now]=++dfsclock;
ha[dfsclock]=now;
st[++dcnt][0]=now;
fir[now]=dcnt;
dep[now]=dep[fa]+1;
par[now]=fa;
for(int v,i=head[now];i;i=Next[i])
if((v=to[i])!=fa)
{
dfs(v,now);
st[++dcnt][0]=now;
}
}
int LCA(int x,int y)
{
if(!x||!y) return 0;
x=fir[x],y=fir[y];
if(x>y) std::swap(x,y);
int d=Log[y+1-x];
return dep[st[x][d]]<dep[st[y-(1<<d)+1][d]]?st[x][d]:st[y-(1<<d)+1][d];
}
struct node
{
int a,b,d;
node(){}
node(int A,int B,int D){a=A,b=B,d=D;}
};
std::vector <node> yuu[N];
int ch[N*50][2],pot[N*50],lca[N*50],L[N*50],R[N*50],root[N],tot;
ll sum[N*50];
#define ls ch[now][0]
#define rs ch[now][1]
void updata(int now)
{
sum[now]=sum[ls]+sum[rs]-dep[LCA(L[rs],R[ls])];
L[now]=L[ls]?L[ls]:L[rs],R[now]=R[rs]?R[rs]:R[ls];
if(lca[ls]&&lca[rs]) lca[now]=LCA(lca[ls],lca[rs]);
else lca[now]=lca[ls]^lca[rs];
}
void ins(int &now,int l,int r,int p,int d)
{
if(!now) now=++tot;
if(l==r)
{
pot[now]+=d;
if(pot[now]) L[now]=R[now]=lca[now]=ha[l],sum[now]=dep[ha[l]];
else L[now]=R[now]=lca[now]=sum[now]=0;
return;
}
int mid=l+r>>1;
if(p<=mid) ins(ls,l,mid,p,d);
else ins(rs,mid+1,r,p,d);
updata(now);
}
int Merge(int now,int las,int l,int r)
{
if(!now||!las) return now^las;
if(l==r)
{
pot[now]+=pot[las];
if(pot[now]) L[now]=R[now]=lca[now]=ha[l],sum[now]=dep[ha[l]];
else L[now]=R[now]=lca[now]=sum[now]=0;
return now;
}
int mid=l+r>>1;
ls=Merge(ls,ch[las][0],l,mid);
rs=Merge(rs,ch[las][1],mid+1,r);
updata(now);
return now;
}
void dfs(int now)
{
for(int v,i=head[now];i;i=Next[i])
if((v=to[i])!=par[now])
{
dfs(v);
root[now]=Merge(root[now],root[v],1,n);
}
for(int i=0;i<yuu[now].size();i++)
{
int a=yuu[now][i].a,b=yuu[now][i].b,d=yuu[now][i].d;
ins(root[now],1,n,dfn[a],d);
ins(root[now],1,n,dfn[b],d);
}
//ll lastans=ans;
ans+=sum[root[now]]-dep[lca[root[now]]];
//printf("%d %lld\n",now,ans-lastans);
}
int main()
{
read(n),read(m);
for(int u,v,i=1;i<n;i++) read(u),read(v),add(u,v),add(v,u);
dfs(1,0);
for(int i=2;i<=dcnt;i++) Log[i]=Log[i>>1]+1;
for(int j=1;j<=18;j++)
{
for(int i=1;i<=dcnt-(1<<j)+1;i++)
{
int x=st[i][j-1],y=st[i+(1<<j-1)][j-1];
st[i][j]=dep[x]<dep[y]?x:y;
}
}
for(int s,t,lca,i=1;i<=m;i++)
{
read(s),read(t);
yuu[s].push_back(node(s,t,1));
yuu[t].push_back(node(s,t,1));
lca=LCA(s,t);
yuu[lca].push_back(node(s,t,-1));
yuu[par[lca]].push_back(node(s,t,-1));
}
dfs(1);
printf("%lld\n",ans>>1);
return 0;
}
2019.5.9
「ZJOI2019」语言 解题报告的更多相关文章
- 【LOJ】#3046. 「ZJOI2019」语言
LOJ#3046. 「ZJOI2019」语言 先orz zsy吧 有一个\(n\log^3n\)的做法是把树链剖分后,形成logn个区间,这些区间两两搭配可以获得一个矩形,求矩形面积并 然后就是对于一 ...
- 「CF670C」Cinema 解题报告
题面 传送门 思路: 离散化.hash 对于这样一个明显的统计排序的题目,当然轻而易举啦~ 但是!看!语言的编号 a数组和 b数组的值最大在\(10^9\)的级别,所以开个数组来存---That's ...
- 「ZJOI2016」旅行者 解题报告
「ZJOI2016」旅行者 对网格图进行分治. 每次从中间选一列,然后枚举每个这一列的格子作为起点跑最短路,进入子矩形时把询问划分一下,有点类似整体二分 至于复杂度么,我不会阿 Code: #incl ...
- 「HNOI2016」树 解题报告
「HNOI2016」树 事毒瘤题... 我一开始以为每次把大树的子树再接给大树,然后死活不知道咋做,心想怕不是个神仙题哦 然后看题解后才发现是把模板树的子树给大树,虽然思维上难度没啥了,但是还是很难写 ...
- 「HNOI2016」序列 解题报告
「HNOI2016」序列 有一些高妙的做法,懒得看 考虑莫队,考虑莫队咋移动区间 然后你在区间内部找一个最小值的位置,假设现在从右边加 最小值左边区间显然可以\(O(1)\),最小值右边的区间是断掉的 ...
- 「HNOI2016」网络 解题报告
「HNOI2016」网络 我有一个绝妙的可持久化树套树思路,可惜的是,它的空间是\(n\log^2 n\)的... 注意到对一个询问,我们可以二分答案 然后统计经过这个点大于当前答案的路径条数,如果这 ...
- 「HAOI2018」染色 解题报告
「HAOI2018」染色 是个套路题.. 考虑容斥 则恰好为\(k\)个颜色恰好为\(c\)次的贡献为 \[ \binom{m}{k}\sum_{i\ge k}(-1)^{i-k}\binom{m-k ...
- 「HNOI2016」最小公倍数 解题报告
「HNOI2016」最小公倍数 考虑暴力,对每个询问,处理出\(\le a,\le b\)的与询问点在一起的联通块,然后判断是否是一个联通块,且联通块\(a,b\)最大值是否满足要求. 然后很显然需要 ...
- 「SCOI2016」围棋 解题报告
「SCOI2016」围棋 打CF后困不拉基的,搞了一上午... 考虑直接状压棋子,然后发现会t 考虑我们需要上一行的状态本质上是某个位置为末尾是否可以匹配第一行的串 于是状态可以\(2^m\)压住了, ...
随机推荐
- js学习笔记-日期对象
<body> <script> var d = new Date() console.log(d) var arr = ['星期日', '星期一', '星期二', '星期三', ...
- 使用nexus3.10搭建maven私有仓库
使用nexus3.10搭建maven私有仓库-----详见如下链接-- --此贴用于笔记 https://blog.csdn.net/vipbupafeng/article/details/80232 ...
- djanjo中url路由匹配规则是啥意思
一,django路由匹配规则的本质是通过正则表达式对用户的url进行匹配. 1,r 是正则表达式中防止转义的符号,例如在python/n代表换行,加上r就不换行了. 2,$ 正则表达式中表示以什么什么 ...
- MySQL忘记密码无法登录的处理办法
MySQL安装在CentOS服务器上. 1.首先确认服务器出于安全的状态,也就是没有人能够任意地连接MySQL数据库.因为在重新设置MySQL的root密码的期间,MySQL数据库完全出于没有密码保护 ...
- Java面试题一览
Java面试题一览
- ubuntu 开机启动shell脚本
1.创建shell启动脚本test 2.将启动脚本复制到 /etc/init.d 目录下 3.设置脚本文件权限 sudo chmod 755 /etc/init.d/test 4.设置脚本启动 sud ...
- nginx支持http2协议
1.http2协议 HTTP 2.0 的主要目标是改进传输性能,实现低延迟和高吞吐量.从另一方面看,HTTP 的高层协议语义并不会因为这次版本升级而受影响.所有HTTP 首部.值,以及它们的使用场景都 ...
- python base64编码实现
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" def encode(b ...
- python基础【第六篇】
list列表 基本结构 lst =[1,2,3,5,6] 为什么学列表? 列表能够存储比字符串更多的数据 列表能够存储各种数据类型 列表的注意点 列表是有序的 列表是可变的,支持索引,切片,步 切片后 ...
- 好947 Mybatis 配置resultMap 带參数查询Map 注意selectOne数据库返回结果一条数据库 否则会报错
//TMD 写几个demo 还有大站採集 <a target=_blank href="http://hao947.com/" target="_blank&quo ...