一边写草稿一边做题吧。要看题解的往下翻,或者是旁边的导航跳一下。

草稿

因为可以开展贸易活动的条件是存在一种通用语 \(L\) 满足 \(u_i\) 到 \(v_i\) 的最短路径上都会 \(L\) 。所以我们考虑能够带来贡献的,只有同一次传教。

但是很有可能在进行当前这一次传教过程中,中间的两座城市已经可以进行贸易往来了,所以我们需要将这些部分的贡献给减去。因为是要其中都有同一种语言,所以我们只需要找连续的有相同语言的就可以了。

相当于对于一次传教,我们需要找出传教路径上的相同颜色的链的长度的城市对。这个东西搞得不好好像要变成 \(O(n^2)\) 。

哦,我好像会了,维护一个结构体,表示当前区间的这个答案是多少,同时从左边开始的链的颜色和长度,右边开始的链的颜色和长度,然后合并的时候就判断一下是不是相同颜色什么的就可以了。

就这?就这?


错了错了,没有这么简单。

我们需要考虑一个区间被多计算了几次,用容斥来做。我们考虑维护一个线段树,表示这个点是否被计算过,连续的一段表示这些点两两都被计算过。

我们考虑添加一条于当前表示计算过的有交的区间该怎么加,应该就直接减去这段区间中被计算过的和再加上这段区间能带来的贡献就好了吧。

这个东西应该还是可以用线段树来维护的。

就这?就这?


错了错了,我错了……

得到了一点提示,对于每一个点,若在一次染色过程中经过了该点,就在该点的集合里添加这两个点的端点,最后求每个点对应集合的最小生成树大小。

嗯,看上去可做多了,接下来考虑怎么去搞这个东西。

因为对于一次染色操作,我们是对这一条链上的所有点的集合中都塞入这两个点,所以我们可以考虑线段树合并。

那么现在就相当于我们有一个点的集合的点(存在线段树里),我们如何求他的最小生成树呢?

我们可以再开一个线段树,处理区间加,最后求有多少个节点的权值是大于 \(0\) 的。因为你每一次添加一对节点,最多在这两个节点的链上产生贡献。然后统计答案,最后除以 \(2\) 就可以了。

然后我们发现前面处理集合的这一棵线段树甚至可以不需要,只需要合并区间加的线段树就可以了吧。。。

复杂度的话区间加的线段树的操作个数是 \(O(n\log_2n)\) 的,然后每一次操作是复杂度是 \(O(\log_2n)\) 的,同时产生 \(O(\log_2n)\) 个节点。所以最终的复杂度应该是 \(O(n\log_2^2n)\) 的。

呼,终于好了。

题解

稍微理一下。

我们对于每一个,其贡献就是经过这个点的链的并。这个东西是可以用类似扫描线的东西来维护的。

然后对于两两节点之间的转移,只用线段树合并和树上差分的思路来完成的。

复杂度是 \(O(n\log_2^2n)\) 的,跑的还挺快,但是线段树合并很容易时空间假掉,需要注意!!!

代码如下

#include<bits/stdc++.h>
using namespace std;
#define Lint long long
const int N=1e5+5;
int n,m;
struct Edge{int nxt,to;}e[N<<1];int fir[N];
void add(int u,int v,int i){e[i]=(Edge){fir[u],v},fir[u]=i;}
struct Node{int dep,fa,top,son,size;}tr[N];
int dfn[N],mp[N],cnt=0;
void dfs1(int u)
{
tr[u].size=1,tr[u].top=u;
for(int i=fir[u];i;i=e[i].nxt)
{
if(e[i].to==tr[u].fa) continue;
tr[e[i].to].fa=u;
tr[e[i].to].dep=tr[u].dep+1;
dfs1(e[i].to);
tr[u].size+=tr[e[i].to].size;
if(tr[e[i].to].size>tr[tr[u].son].size)
tr[u].son=e[i].to;
}
}
void dfs2(int u)
{
dfn[++cnt]=u,mp[u]=cnt;
if(tr[u].son)
{
tr[tr[u].son].top=tr[u].top;
dfs2(tr[u].son);
}
for(int i=fir[u];i;i=e[i].nxt)
{
if(e[i].to==tr[u].fa||e[i].to==tr[u].son) continue;
dfs2(e[i].to);
}
}
struct Seg_Tree
{
int rt[N],now,top,rub[N<<5];
struct Node{int ls,rs,data,tag,alive;}tr[N<<5];
Seg_Tree(){now=top=0,memset(rub,0,sizeof(rub));}
int newnode(){return top?rub[top--]:++now;}
void recycle(int x){tr[x]=(Node){0,0,0,0,0},rub[++top]=x;}
void up(int u,int l,int r)
{
if(tr[u].tag) tr[u].data=r-l+1;
else tr[u].data=tr[tr[u].ls].data+tr[tr[u].rs].data;
}
void copy(int u,int v)
{
tr[u]=tr[v];
tr[tr[u].ls].alive++;
tr[tr[u].rs].alive++;
}
void add(int u,int l,int r,int x,int y,int z)
{
if(x<=l&&r<=y){tr[u].tag+=z,up(u,l,r);return ;}
int mid=(l+r)>>1;
if(x<=mid)
{
if(!tr[u].ls) tr[u].ls=newnode(),tr[tr[u].ls].alive++;
add(tr[u].ls,l,mid,x,y,z);
}
if(y>mid)
{
if(!tr[u].rs) tr[u].rs=newnode(),tr[tr[u].rs].alive++;
add(tr[u].rs,mid+1,r,x,y,z);
}
up(u,l,r);
}
void del(int u)
{
if(tr[u].ls) tr[tr[u].ls].alive--;
if(tr[u].rs) tr[tr[u].rs].alive--;
if(tr[u].ls&&!tr[tr[u].ls].alive) del(tr[u].ls);
if(tr[u].rs&&!tr[tr[u].rs].alive) del(tr[u].rs);
recycle(u);
}
void merge(int u,int v,int l,int r)
{
tr[u].tag+=tr[v].tag;
int mid=(l+r)>>1;
if(tr[v].ls)
{
if(!tr[u].ls) tr[u].ls=newnode(),copy(tr[u].ls,tr[v].ls);
else merge(tr[u].ls,tr[v].ls,l,mid);
}
if(tr[v].rs)
{
if(!tr[u].rs) tr[u].rs=newnode(),copy(tr[u].rs,tr[v].rs);
else merge(tr[u].rs,tr[v].rs,mid+1,r);
}
up(u,l,r);
}
}t;
void work(int rt,int u,int v,int z)
{
while(tr[u].top!=tr[v].top)
{
if(tr[tr[u].top].dep<tr[tr[v].top].dep) swap(u,v);
t.add(rt,1,n,mp[tr[u].top],mp[u],z);
u=tr[tr[u].top].fa;
}
if(tr[u].dep>tr[v].dep) swap(u,v);
t.add(rt,1,n,mp[u],mp[v],z);
}
int lca(int u,int v)
{
while(tr[u].top!=tr[v].top)
{
if(tr[tr[u].top].dep<tr[tr[v].top].dep) swap(u,v);
u=tr[tr[u].top].fa;
}
if(tr[u].dep>tr[v].dep) swap(u,v);
return u;
}
vector<pair<pair<int,int>,int> > bag[N];
Lint res=0;
void dfs(int u)//n
{
t.rt[u]=t.newnode();
for(int i=fir[u];i;i=e[i].nxt)
{
if(e[i].to==tr[u].fa) continue;
dfs(e[i].to);
t.merge(t.rt[u],t.rt[e[i].to],1,n);//log_2^2n
t.del(t.rt[e[i].to]);//log_2^2n
}
for(int i=0;i<(int)bag[u].size();++i)
work(t.rt[u],bag[u][i].first.first,bag[u][i].first.second,bag[u][i].second);
if(t.tr[t.rt[u]].data) res+=t.tr[t.rt[u]].data-1;
}
int main()
{
cin>>n>>m;
for(int i=1,u,v;i<n;++i)
{
scanf("%d%d",&u,&v);
add(u,v,i<<1),add(v,u,i<<1|1);
}
dfs1(1),dfs2(1);
while(m--)
{
int u,v;
scanf("%d%d",&u,&v);
int tmp=lca(u,v);
bag[u].push_back(make_pair(make_pair(u,v),1));
bag[v].push_back(make_pair(make_pair(u,v),1));
bag[tmp].push_back(make_pair(make_pair(u,v),-1));
bag[tr[tmp].fa].push_back(make_pair(make_pair(u,v),-1));
}
dfs(1);
printf("%lld\n",res/2);
return 0;
}

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

  1. 题解 P5327 [ZJOI2019]语言

    P5327 [ZJOI2019]语言 解题思路 暴力 首先讲一下我垃圾的 40pts 的暴力(其他 dalao 都是 60pts 起步): 当然评测机快的话(比如 LOJ 的),可以卡过 3,4 个点 ...

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

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

  3. Luogu P5327 [ZJOI2019]语言

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

  4. [ZJOI2019]语言

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

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

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

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

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

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

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

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

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

  9. [LOJ3046][ZJOI2019]语言:树链的并+线段树合并

    分析 问题显然可以转化为对于每个节点询问所有这个节点的所有链的链并的大小. 考场上我直接通过树剖打标记+树剖线段树维护以\(O(n \log^3 n)\)的时间复杂度暴力实现了这个过程.(使用LCT或 ...

随机推荐

  1. ngx ------ngx_cache_manager_process_cycle

    static void ngx_cache_manager_process_cycle(ngx_cycle_t *cycle, void *data) {----------------------- ...

  2. 三、分布式编程总结------linux多线程服务端编程

  3. 异常记录-Dialog样式踩坑

    好久没记录文档了,拖了老半个月,终于空下来时间,为了避免以后踩坑,必须记录记录. 背景: 为activity设置样式为弹窗activity 异常一: activity设置style后,布局不能够正常显 ...

  4. Fiddler的一系列学习瞎记2(没有章法的笔记)

    前言 不适合小白,因为很多需要小白来掌握的东西我都没有写,就是补充自己还不会的东西,所以,有些同僚看起来可能感觉不是很清楚. 正文: 瞎记2-什么是代理服务器 1.web代理服务器,是在客户端和服务器 ...

  5. 配置cobbler步骤

    首先找到下载包的地址 (使用的是centos6) http://download.opensuse.org/repositories/home:/libertas-ict:/cobbler26/Cen ...

  6. mysql 创建数据库知识总结

    表设计 库名.表名.字段名必须使用小写字母,"_"分割,且名称长度不超过12个字符并且要做到见名知意. 建议使用InnoDB存储引擎. 存储精确浮点数必须使用DECIMAL替代FL ...

  7. 考研党其实可以用思维导图MindManager做考研复习计划

    近年由于就业压力,个人学历提升等各种原因,考研的人数越来越多了,相对难度也越来越大了,尽管今年研究生招生规模同比去年增加18.9万,但也无法掩盖考研的竞争逐年激烈. 身为考研大军中的预备选手之一,小编 ...

  8. 如何将MathType恢复出厂设置

    必大家都知道,我们日常使用的手机是自带恢复出厂设置功能的,其实除了手机,咱们今天要说的这款公式编辑器MathType,也是可以进行恢复出厂设置操作的哦,下面就让小编给大家介绍一下吧. 一.打开Math ...

  9. Python基础整理,懒得分类了,大家对付看看吧

    第一次搞这么多图

  10. PHP 统计文件数和文件大小

    /** * 统计文件数和文件大小 */private function getFileCacheCount($pathName){ $data = [ 'num' => 0, 'size' =& ...