oj:https://gxyzoj.com/d/gxyznoi/p/P35
树上差分+线段树合并+树链剖分
1. 暴力
从最基础的方法开始,暴力统计s-t上的点,然后用map直接统计,显然会T,20pts
2. 特殊性质
测试点5,6保证了数据是一条链,将链上每一个节点编号为1-n
对于每一次操作,可以记录其覆盖情况,设共有x个节点,有y个节点已被覆盖,则答案需增加\((x-1)\times(x-y)\)
如上操作,显然可以用线段树维护覆盖情况
3. 正解
利用虚树思想,若一个节点v可以与u进行贸易,则从u向v连一条边,则这棵生成树的大小-1就是可以与u进行贸易的节点个数
可以将链的方法迁移到树上
首先,对原树求dfs序,然后对于每次操作进行树剖,将每一次操作分成一些连续的区间
再考虑如何用线段树维护,转变一下思维,可以将记录是否被覆盖的数组改为记录覆盖次数
所以可以利用树上差分,在s,t处对所有区间分别加一,再在\(fa_lca\)处减去即可
然后考虑如何转移,对于每个节点,连接状态显然分为两部分
一部分是该点本身的权值,直接将预处理好的区间相加即可
另一部分是由儿子转移而来,这部分可以使用线段树合并解决
最后统计答案,每次转移后的的个数显然就是覆盖次数不为0的点数,可以另开一个数组val记录
注意,要用动态开点线段树,数组尽量开到n的128倍以上,不要写一些奇怪的语句
4. 代码
#include<cstdio>
#include<vector>
#include<algorithm>
#define ll long long
using namespace std;
int n,m,head[100005],edgenum;
struct edge{
int to,nxt;
}e[200005];
void add_edge(int u,int v)
{
e[++edgenum].nxt=head[u];
e[edgenum].to=v;
head[u]=edgenum;
}
int dep[100005],son[100005],size[100005],top[100005],fa[100005];
struct node{
int l,r,val;
};
struct node1{
int l,r;
};
vector<node> a[100005];
vector<node1> tmp;
void dfs(int u,int f)
{
size[u]=1;
fa[u]=f;
dep[u]=dep[f]+1;
int maxn=-1;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==fa[u]) continue;
dfs(v,u);
size[u]+=size[v];
if(maxn<size[v])
{
maxn=size[v];
son[u]=v;
}
}
}
int dfn[100005],idx,pre[100005];
void dfs2(int u,int t)
{
// printf("%d %d\n",u,t);
dfn[u]=++idx;
top[u]=t;
pre[idx]=u;
a[u].push_back((node){dfn[u],dfn[u],1});
if(fa[u])
{
a[fa[u]].push_back((node){dfn[u],dfn[u],-1});
}
if(son[u])
{
dfs2(son[u],t);
}
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==fa[u]||v==son[u]) continue;
dfs2(v,v);
}
}
int lca(int u,int v)
{
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
tmp.push_back((node1){dfn[top[u]],dfn[u]});
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
tmp.push_back((node1){dfn[u],dfn[v]});
return u;
}
ll lazy[12800005];
int val[12800005],ls[12800005],rs[12800005];
void pushup(int id,int l,int r)
{
if(lazy[id]>0) val[id]=r-l+1;
else val[id]=val[ls[id]]+val[rs[id]];
}
int cnt;
int update(int id,int l,int r,int ql,int qr,int v)
{
if(!id) id=++cnt;
if(l>=ql&&r<=qr)
{
lazy[id]+=v;
pushup(id,l,r);
return id;
}
int mid=(l+r)>>1;
if(ql<=mid)
ls[id]=update(ls[id],l,mid,ql,qr,v);
if(qr>mid)
rs[id]=update(rs[id],mid+1,r,ql,qr,v);
pushup(id,l,r);
return id;
}
int merge(int p,int q,int l,int r)
{
if(p==0||q==0) return p+q;
lazy[p]+=lazy[q];
if(l==r)
{
pushup(p,l,r);
return p;
}
int mid=(l+r)>>1;
ls[p]=merge(ls[p],ls[q],l,mid);
rs[p]=merge(rs[p],rs[q],mid+1,r);
pushup(p,l,r);
return p;
}
ll ans;
int rt[100005];
void solve(int u)
{
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if(v==fa[u]) continue;
solve(v);
rt[u]=merge(rt[u],rt[v],1,n);
}
for(int i=0;i<a[u].size();i++)
{
rt[u]=update(rt[u],1,n,a[u][i].l,a[u][i].r,a[u][i].val);
}
ans=ans+val[rt[u]];
}
int main()
{
// freopen("1.in","r",stdin);
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add_edge(u,v);
add_edge(v,u);
rt[i]=i;
}
rt[n]=n;
cnt=n;
dfs(1,0);
// printf("1");
dfs2(1,1);
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
int lc=lca(u,v);
for(int j=0;j<tmp.size();j++)
{
int l1=tmp[j].l,r1=tmp[j].r;
if(l1>r1) swap(l1,r1);
a[u].push_back((node){l1,r1,1});
a[v].push_back((node){l1,r1,1});
if(fa[lc]) a[fa[lc]].push_back((node){l1,r1,-2});
}
tmp.clear();
}
solve(1);
printf("%lld",(ans-n)/2);
return 0;
}
标签:P5327,val,rs,int,100005,fa,解题,ZJOI2019,id
From: https://www.cnblogs.com/wangsiqi2010916/p/18125561