采药人的路径 bzoj-3697
题目大意:给你一个n个节点的树,每条边分为阴性和阳性,求满足条件的链的个数,使得这条链上阴性的边的条数等于阳性的边的条数,且这条链上存在一个节点,这个节点到一个端点的链也是阴阳相等,到另一个顶点也是阴阳相等。
注释:$1\le n \le 10^5$。
想法:点分治裸题,开始净想着怎么在一条阴阳相等的链上找点了。其实可以在找初步满足条件的链是就直接处理出完全满足条件者。首先,我们先令阳性为1,阴性为-1,求边权和为0即为阴阳相等。紧接着,我们定义
f[i][1]表示root的子树中满足到重心的链且边权和为i、存在一个点使得该点到这条链上到不是重心的另一个节点的边权和为0的条数。
f[i][0]同理,只是不存在这样的点。
g[i][1/0]表示边权和为-i。
特别地,因为所有的边权都是1 or -1,所以我可以在dfs链的时候直接判断出是否存在这样的节点。具体地:这条链的边权是x,如果这个节点相对于重心是根节点来讲存在一个祖先,使得这个祖先到根节点的边权和也是x,那么显然就存在这样的节点,就是那个祖先。然后,我们可以用l和r分别表示这个点之前祖先的边权和所能达到的下界和上界。因为边权是1 or -1,所以$[l,r]$之间的所有值都是可以取到的。这就处理出了f和g数组。
剩下的,就是运用单步容斥和简单的点分治模板套用,注意size的问题(抄的GXZlegend的代码,所以就没特意更改size的错误)。
最后,附上丑陋的代码... ...
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int main()
{
}#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 100010
using namespace std;
int head[N],to[N<<1],val[N<<1],next[N<<1],cnt,vis[N],si[N],ms[N],sum,root,md;
long long f[N][2],g[N][2],ans;
inline void add(int x,int y,int z)
{
to[++cnt]=y,val[cnt]=z,next[cnt]=head[x],head[x]=cnt;
}
void getroot(int x,int fa)
{
si[x]=1,ms[x]=0;
for(int i=head[x];i;i=next[i])
if(!vis[to[i]]&&to[i]!=fa)
getroot(to[i],x),si[x]+=si[to[i]],ms[x]=max(ms[x],si[to[i]]);
ms[x]=max(ms[x],sum-si[x]);
if(ms[x]<ms[root]) root=x;
}
void calc(int x,int fa,int now,int cnt)
{
if(now==0)
{
if(cnt>=2)ans++;
cnt++;
}
for(int i=head[x];i;i=next[i])
if(!vis[to[i]]&&to[i]!=fa)
calc(to[i],x,now+2*val[i]-1,cnt);
}
void dfs(int x,int fa,int now,int l,int r)
{
if(now>=l&&now<=r)
{
if(now>=0)f[now][1]++;
else g[-now][1]++;
}
else
{
if(now>=0)f[now][0]++;
else g[-now][0]++;
}
l=min(l,now),r=max(r,now),md=max(md,max(-l,r));
for(int i=head[x];i;i=next[i])
if(!vis[to[i]]&&to[i]!=fa)
dfs(to[i],x,now+val[i]*2-1,l,r);
}
void solve(int x)
{
vis[x]=1,md=0,calc(x,0,0,0);
dfs(x,0,0,1,-1),ans+=f[0][1]*(f[0][1]-1)/2,f[0][0]=f[0][1]=0;
for(int i=1;i<=md;i++)ans+=f[i][0]*g[i][1]+f[i][1]*g[i][0]+f[i][1]*g[i][1],f[i][0]=f[i][1]=g[i][0]=g[i][1]=0;
for(int i=head[x];i;i=next[i])
{
if(!vis[to[i]])
{
md=0,dfs(to[i],0,2*val[i]-1,0,0),ans-=f[0][1]*(f[0][1]-1)/2,f[0][0]=f[0][1]=0;
for(int j=0;j<=md;j++)ans-=f[j][0]*g[j][1]+f[j][1]*g[j][0]+f[j][1]*g[j][1],f[j][0]=f[j][1]=g[j][0]=g[j][1]=0;
sum=si[to[i]],root=0,getroot(to[i],0),solve(root);
}
}
}
int main()
{
int n,x,y,z;
scanf("%d",&n);
for(int i=1;i<n;i++)scanf("%d%d%d",&x,&y,&z),add(x,y,z),add(y,x,z);
sum=n;
root=0;
ms[0]=n;
getroot(1,0);
solve(root);
printf("%lld\n",ans);
return 0;
}
小结:在更新l和r时now的值并没有进行更改,所以不需要特殊处理,也就是当前的calc的now是可以直接拿来用的。