Problem
我们有一颗从1到n编号的n(<=300000)个结点的树,此外,您将从树中获得M(<=300000)个节点对,形式为(a1,b1),(a2,b2),…(am,bm).
我们需要给每一条边定向,使得每一对节点对存在一条从ai到bi或从bi到ai的路径。
现在要求方案数,对10^9+7取mod即可。
Solution
- 刚看这道题,感觉很神仙。
- 仔细分析,对于一个点对(a,b),显然a到b的路径上的边只要确定一条的方向,其他的均可确定。
- 那么可以将这些边丢到同一个并查集中。(并查集中的每一个点代表原图中的一条边)那么最终的答案即为 2s 2 s (s为并查集数)。
- 如何实现呢?我先讲一下我的SB方法。
我的SB方法
- 具体地说,对于点对(a,b),找出其lca,设为点c。我们使用倍增找出c到a路径上第二个点x和c到b路径上第二个点y(x、y是c的儿子),那么我们让并查集中的
c→x
c
→
x
连向
c→y
c
→
y
。
- 在上图中,我们先让边3连向边4。然后,我们再dfs一波,让1连向2,2连向3,6连向5,5连向4。
- 具体地说,我们可以采用树上差分。在a、b处各打一个+1,在x、y处各打一个-1。每个点的值大于0则表明它连向它父亲的边 应连向 它父亲连向它祖父的边。举例来说,点a的值就大于0,那么边1就应连向边2。
- 但是这么做还有一个问题:那就是无法处理答案=0的情况。
- 答案之所以会=0,无非是因为有些点对矛盾了。
- 我们可以记录一下并查集中的每个点是否和它的父亲同向。(并查集中的每一个点代表原图中的一条边)
- 这么做的话,路径压缩就要改一下。
- 我们可以求个后缀异或和。
- 举例来说,对于 1→2→3→4 1 → 2 → 3 → 4 ( x→y x → y 表示y为x的父亲),我们要将1路径压缩,即将原树变成 1→4,2→4,3→4 1 → 4 , 2 → 4 , 3 → 4 的形态。若1与2反向,2与3同向,3与4反向,我们倒着来做,求一波后缀和,于是可知3与4反向,2与4反向,1与4同向。
- 还有连边的时候也要有所修改。
- 对于 x→y x → y ,我们都是让x和y路径压缩一波,然后设它们的父亲分别为fx、fy,令fx连向fy。
- 考虑一下,若x与fx反向,y与fy同向,我们要从x连与y同向的边,那么就应从fx连与fy反向的边。
- 实际上,fx连向fy的边即为x与fx的边、y与fy的边、x与y的边的异或和。
- 那么何时会出现矛盾呢?
- 当x和y位于同一个并查集中时,路径压缩后,fx=fy。设若此时我们要从fx连与fy反向的边,即从fx连与fx反向的边;但fx肯定与fx同向,这就产生矛盾。
- 所以,一出现这种情况,直接输出0即可。
- 时间复杂度: O(mlog2n+nα(n)) O ( m l o g 2 n + n α ( n ) ) 。
Code
#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define rep(i,x) for(edge *i=x; i; i=i->ne)
#define P {putchar(48); exit(0);}
#define fs(x) for(it=x.begin();it!=x.end();it++)
using namespace std;
const int N=3e5+1,M=1e9+7;
int i,n,m,x,y,sum[N],top,sta[N],f[N],dep[N],anc[N][19],a,b,q,fa[N],d[N],ans;
bool vis[N],re[N];
struct edge
{
int v;
edge *ne;
edge(int v,edge *ne):v(v),ne(ne){}
}*fin[N],*cur[N];
void read(int&x)
{
char ch=' '; x=0;
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48), ch=getchar();
}
inline void link(int x,int y)
{
fin[x]=new edge(y,fin[x]);
}
void dfs(int x)
{
sta[top=1]=x;
while(top)
{
loop:x=sta[top];
if(!vis[x])
{
vis[x]=1; cur[x]=fin[x]; anc[x][0]=f[x];
fo(i,1,18) anc[x][i]=anc[anc[x][i-1]][i-1];
}
rep(i,cur[x])
{
y=i->v;
if(y!=f[x])
{
f[y]=x; dep[y]=dep[x]+1;
sta[++top]=y; cur[x]=i->ne; goto loop;
}
}
top--;
}
}
int lca(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
int i,fx,fy;
fd(i,18,0) if((fx=anc[x][i])&&dep[fx]>=dep[y]) x=fx;
if(x!=y)
fd(i,18,0)
if((fx=anc[x][i])!=(fy=anc[y][i]))
x=fx, y=fy;
return x==y ? x : f[x];
}
int mindep(int x,int y)
{
int i,f;
fd(i,18,0) if((f=anc[x][i])&&dep[f]>dep[y]) x=f;
return x;
}
int gef(int x)
{
d[0]=0;
for(; x!=fa[x]; x=fa[x]) d[++d[0]]=x;
bool s=0;
while(d[0])
{
s^=re[d[d[0]]];
fa[d[d[0]]]=x;
re[d[d[0]--]]=s;
}
return x;
}
void Union(int x,int y,bool k)
{
int fx=gef(x), fy=gef(y);
k=k^re[x]^re[y];
if(fx==fy&&k) P;
fa[fx]=fy; re[fx]=k;
}
void dfs1(int x)
{
sta[top=1]=x;
while(top)
{
loop:x=sta[top];
if(vis[x]) vis[x]=0, cur[x]=fin[x];
rep(i,cur[x])
{
y=i->v;
if(y!=f[x])
{
sta[++top]=y;
cur[x]=i->ne; goto loop;
}
}
sum[f[x]]+=sum[x];
if(sum[x]>0) Union(x,f[x],0);
top--;
}
}
int main()
{
freopen("usmjeri.in","r",stdin);
freopen("usmjeri.out","w",stdout);
read(n); read(m);
fo(i,1,n-1)
{
read(x); read(y);
link(x,y); link(y,x);
}
dfs(1);
fo(i,1,n) fa[i]=i;
fo(i,1,m)
{
read(a); read(b);
if(a==b) continue;
q=lca(a,b);
if(q==a||q==b)
{
if(q==a)
x=mindep(b,a), sum[b]++;
else x=mindep(a,b), sum[a]++;
sum[x]--; continue;
}
sum[a]++; sum[b]++;
x=mindep(a,q); sum[x]--;
y=mindep(b,q); sum[y]--;
Union(x,y,1);
}
dfs1(1);
ans=1;
fo(i,2,n)
{
x=gef(i);
if(vis[x]) continue;
vis[x]=1; (ans<<=1)%=M;
}
printf("%d",ans);
}
正确的正解
- 实际上,对于每一个点对(a,b),我们在找出c=lca(a,b)后,可以直接暴力连上a到c的边以及b到c的边。
- 比如对于上图,我们暴力将边1连上边2,边2连上边3,边6连上边5,边5连上边4。
- 不过,我们连边是让深度较大的边连向深度较小的边。
- 这样一来,处理完点对(a,b)后,如果有另一对(a’,b’),其lca为c’,且路径(a’,c’)完全覆盖了(a,c),则我们在连这段路径的边时会直接跳过整段的路径(a,b)。(我们要路径压缩)
- 但是,如何将路径(a,c)和路径(b,c)弄到同一个并查集呢?
- 其实我们不必找到x和y。
- 设边a表示点a连向其父亲的边,譬如上图中的边a即为边1。我们可以直接将边a和边b连起来。
- 但是这样会出现一个问题。
- 边a和边b间连的边是表示它们反向(边a和边b反向)。这样,我们在暴力连其他的(a’,c’)路径时,可能(a’,c’)上的路径中有边反向。
- 如果我们从某条边x,跳到了它所在的并查集的根,压缩路径,变成fa[x]后,直接判断边a是否和边fa[x]同向,那也依然有问题。因为可能x到fa[x]的路径上就是有两个点(并查集中的点代表原图中的一条边)反向,我们难以判断。
- 囿于上述问题,我们可以先处理所有(a,c),(b,c)的路径,即先连同向的边。连完同向的边后,再扫一遍询问数组,为所有边a和边b再连上反向的边。
- 时间复杂度: O(mlog2n+nα(n)) O ( m l o g 2 n + n α ( n ) ) 。
Code
#include <bits/stdc++.h>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
#define rep(i,x) for(edge *i=x; i; i=i->ne)
#define P {putchar(48); exit(0);}
#define fs(x) for(it=x.begin();it!=x.end();it++)
using namespace std;
const int N=3e5+1,M=1e9+7;
int i,n,m,x,y,sum[N],top,sta[N],f[N],dep[N],anc[N][19],a,b,q,fa[N],d[N],ans;
bool vis[N],re[N];
struct edge
{
int v;
edge *ne;
edge(int v,edge *ne):v(v),ne(ne){}
}*fin[N],*cur[N];
struct tuple
{
int a,b,c;
}t[N];
void read(int&x)
{
char ch=' '; x=0;
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48), ch=getchar();
}
inline void link(int x,int y)
{
fin[x]=new edge(y,fin[x]);
}
void dfs(int x)
{
sta[top=1]=x;
while(top)
{
loop:x=sta[top];
if(!vis[x])
{
vis[x]=1; cur[x]=fin[x]; anc[x][0]=f[x];
fo(i,1,18) anc[x][i]=anc[anc[x][i-1]][i-1];
}
rep(i,cur[x])
{
y=i->v;
if(y!=f[x])
{
f[y]=x; dep[y]=dep[x]+1;
sta[++top]=y; cur[x]=i->ne; goto loop;
}
}
top--;
}
}
int lca(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
int i,fx,fy;
fd(i,18,0) if((fx=anc[x][i])&&dep[fx]>=dep[y]) x=fx;
if(x!=y)
fd(i,18,0)
if((fx=anc[x][i])!=(fy=anc[y][i]))
x=fx, y=fy;
return x==y ? x : f[x];
}
int gef(int x)
{
d[0]=0;
for(; x!=fa[x]; x=fa[x]) d[++d[0]]=x;
bool s=0;
while(d[0])
{
s^=re[d[d[0]]];
fa[d[d[0]]]=x;
re[d[d[0]--]]=s;
}
return x;
}
void go(int x)
{
for(x=gef(x); dep[x]>dep[q]+1; x=gef(x)) fa[x]=gef(f[x]);
}
int main()
{
freopen("usmjeri.in","r",stdin);
freopen("usmjeri.out","w",stdout);
read(n); read(m);
fo(i,1,n-1)
{
read(x); read(y);
link(x,y); link(y,x);
}
dfs(1);
fo(i,1,n) fa[i]=i;
fo(i,1,m)
{
read(a); read(b);
q=lca(a,b);
go(a); go(b);
t[i]={a,b,q};
}
fo(i,1,m)
{
a=t[i].a; b=t[i].b; q=t[i].c;
if(a==q|b==q) continue;
int fx=gef(a), fy=gef(b);
if(fx==fy)
{
if(re[a]==re[b]) P;
continue;
}
fa[fx]=fy; re[fx]=re[a]==re[b];
}
ans=1;
fo(i,2,n)
{
x=gef(i);
if(!vis[x]) continue;
vis[x]=0; (ans<<=1)%=M;
}
printf("%d",ans);
}