「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
",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
",ans>>1);
return 0;
}
2019.5.9