luogu P5306 [COCI2019] Transport
闲聊
看到树上路径问题点分治肯定可以做
看了一下其他题解好像没有具体介绍如何不重不漏地计数以及一些细节(其实是我太菜了没看懂
蒟蒻我做点分题还没有用过容斥定理,一般都是在每颗子树里跑一遍的时候直接累加前面已经跑过的贡献QWQ
前置芝士
- 点分治
- 平衡树(其实可以不用,
但因为我不太会容斥,而且这题边权比较大树状数组维护不了
大体思路
(后面会详细介绍如何累计答案
首先点分治- 考虑对分治中心的每一颗子树跑两遍,(鉴于方便,先说第二遍的作用)第二遍算从每个点能否到分治中心,如果能到的话到了以后还剩多少油并插进Treap里,第一遍跑的时候直接合并分治中心到当前子树点的路径和 与 之前Treap里的计算一下就OK了(具体怎么维护我觉得楼上写的特别清楚就不赘述了)
- Upd 7.20: 算了还是讲一讲这个具体怎么往Treap里插吧(顺便修一下错别字)。首先第一遍DFS,这个点到分治中心的任意过程中都不能出现油量为负数的情况;第二遍DFS,分治中心到这个点的过程中出现的最小油量 加上 Treap里目前的最大油量应该大于等于0
- 至于两遍DFS的顺序问题应该很显然(就是因为这样所以不用容斥了
- 那么这样算下来,你会发现答案少了很多,因为我们枚举到终点的路径(也就是分治中心到某个节点的路径)时只有前面跑过的子树会对它产生贡献,但是目前没跑过的子树也会对它产生贡献
- 解决方法是每次分治的时候记一下分治中心即将访问的每棵子树,先正着跑一遍,再倒着跑一遍就可以把答案全累加了
如何累加答案(重点来啦)
- 上文说到一共要跑两遍,这就会引发一些奇奇怪怪的问题,比如分治中心与子树节点形成的点对会被加两次,下面就来说一说我的处理方法
- 计算点对(分治中心,子树节点)的贡献:注意到累加以根为起点产生的贡献其实就是把根(分治中心)拥有的汽油量插进Treap里,这样我们在正着跑的时候不管他,倒着跑之前\(Insert(point[分治中心])\)就可以了
- 计算点对(子树节点,分支中心)的贡献:我的处理方法是用一个变量ans_1来临时记下贡献,累加给答案的时候除以2就好啦
- 至于其他起点和终点都不是分治中心的点对,我们的做法能很好的应对
这题的细节其实还是非常多的,有不懂的地方可以私我QWQ
上代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100009;
typedef long long LL;
int n,point[N],head[N],cnt,siz[N],del[N],l[N],tmp,pool,root,w[N],tot;
LL ans,all[N],ans_1;
struct Edge
{
int nxt,to,w;
}g[2*N];
struct Tree
{
int pri,l,r,cnt,siz;
LL val;
#define p(x) a[x].pri
#define v(x) a[x].val
#define l(x) a[x].l
#define r(x) a[x].r
#define c(x) a[x].cnt
#define s(x) a[x].siz
}a[N*10];
void Zig(int &k)
{
int x=l(k);
l(k)=r(x);
r(x)=k;
s(x)=s(k);
s(k)=s(l(k))+s(r(k))+c(k);
k=x;
}
void Zag(int &k)
{
int x=r(k);
r(k)=l(x);
l(x)=k;
s(x)=s(k);
s(k)=s(l(k))+s(r(k))+c(k);
k=x;
}
void Insert(int &k,LL x)
{
if(!k)
{
k=++pool,v(k)=x,c(k)=s(k)=1,p(k)=rand();
return;
}
s(k)++;
if(x==v(k))
c(k)++;
else if(x<v(k))
{
Insert(l(k),x);
if(p(l(k))<p(k))
Zig(k);
}
else
{
Insert(r(k),x);
if(p(r(k))<p(k))
Zag(k);
}
}
void Delete(int &k,LL x)
{
if(v(k)==x)
{
if(c(k)>1)
c(k)--,s(k)--;
else if(!l(k)||!r(k))
k=l(k)+r(k);
else if(p(l(k))<p(r(k)))
Zig(k),Delete(k,x);
else
Zag(k),Delete(k,x);
return;
}
s(k)--;
if(x<v(k))
Delete(l(k),x);
else
Delete(r(k),x);
}
int Query(LL x)//计算当前Treap里>=x的数有多少个
{
int k=root,ans=0;
while(k)
{
if(x<v(k))
ans+=c(k)+s(r(k)),k=l(k);
else if(x>v(k))
k=r(k);
else
return ans+c(k)+s(r(k));
}
return ans;
}
void add(int from,int to,int w)
{
g[++cnt].nxt=head[from];
g[cnt].to=to;
g[cnt].w=w;
head[from]=cnt;
}
void init()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
scanf("%d",&point[i]);
int x,y,z;
for (int i=1;i<n;i++)
{
scanf("%d %d %d",&x,&y,&z);
add(x,y,z),add(y,x,z);
}
}
void dfs(int x,int fa)//计算子树大小
{
siz[x]=1;
for (int i=head[x];i;i=g[i].nxt)
{
int v=g[i].to;
if(v==fa||del[v])
continue;
dfs(v,x);
siz[x]+=siz[v];
}
}
int Get_Weight(int x)//找重心
{
dfs(x,-1);
int k=siz[x]/2,fa=-1;
while(1)
{
int tmp=0;
for (int i=head[x];i;i=g[i].nxt)
{
int v=g[i].to;
if(del[v]||v==fa)
continue;
if(siz[v]>siz[tmp])
tmp=v;
}
if(siz[tmp]<=k)
return x;
fa=x,x=tmp;
}
}
void DFS(int x,int fa,LL Max,LL now)//第一遍DFS
{
ans+=Query(Max);
for (int i=head[x];i;i=g[i].nxt)
{
int v=g[i].to;
if(v==fa||del[v])
continue;
DFS(v,x,max(Max,now-point[x]+g[i].w),now-point[x]+g[i].w);
}
}
void DFS_1(int x,int fa,LL Max,LL now)//第二遍DFS
{
if(now>=Max)
Insert(root,now),all[++tot]=now,ans_1++;
for (int i=head[x];i;i=g[i].nxt)
{
int v=g[i].to;
if(v==fa||del[v])
continue;
DFS_1(v,x,max(now,Max),now+point[v]-g[i].w);
}
}
void Get_Ans(int x)
{
tmp=tot=ans_1=0;
for (int i=head[x];i;i=g[i].nxt)
{
int v=g[i].to;
if(del[v])
continue;
l[++tmp]=v,w[tmp]=g[i].w;
}
//正着跑一遍
for (int i=1;i<=tmp;i++)
{
DFS(l[i],x,w[i],w[i]);
DFS_1(l[i],x,point[x],point[x]+point[l[i]]-w[i]);
}
while(tot)
Delete(root,all[tot--]);//清空防止对以后产生影响
//倒着跑一遍
Insert(root,point[x]);//分治中心的油量第二遍再加进来
for (int i=tmp;i>=1;i--)
{
DFS(l[i],x,w[i],w[i]);
DFS_1(l[i],x,point[x],point[x]+point[l[i]]-w[i]);
}
while(tot)
Delete(root,all[tot--]);
Delete(root,point[x]);
ans+=ans_1/2;//点对(子树节点,分治中心)的贡献最后要除以二
}
void conquer(int x)
{
int w=Get_Weight(x);
Get_Ans(w);
del[w]=1;
for (int i=head[w];i;i=g[i].nxt)
{
int v=g[i].to;
if(del[v])
continue;
conquer(v);
}
}
void work()
{
Insert(root,-(1LL<<60)),conquer(1);//加入负无穷大是为了怕Query出锅
printf("%lld\n",ans);
}
int main()
{
init();
work();
return 0;
}
由于博主比较菜,所以有很多东西待学习,大部分文章会持续更新,另外如果有出错或者不周之处,欢迎大家在评论中指出!