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的路徑上的邊只要確定一條的方向,其他的均可確定。
- 那麼可以將這些邊丟到同一個並查集中。(並查集中的每一個點代表原圖中的一條邊)那麼最終的答案即爲 (s爲並查集數)。
- 如何實現呢?我先講一下我的SB方法。
我的SB方法
- 具體地說,對於點對(a,b),找出其lca,設爲點c。我們使用倍增找出c到a路徑上第二個點x和c到b路徑上第二個點y(x、y是c的兒子),那麼我們讓並查集中的 連向 。
- 在上圖中,我們先讓邊3連向邊4。然後,我們再dfs一波,讓1連向2,2連向3,6連向5,5連向4。
- 具體地說,我們可以採用樹上差分。在a、b處各打一個+1,在x、y處各打一個-1。每個點的值大於0則表明它連向它父親的邊 應連向 它父親連向它祖父的邊。舉例來說,點a的值就大於0,那麼邊1就應連向邊2。
- 但是這麼做還有一個問題:那就是無法處理答案=0的情況。
- 答案之所以會=0,無非是因爲有些點對矛盾了。
- 我們可以記錄一下並查集中的每個點是否和它的父親同向。(並查集中的每一個點代表原圖中的一條邊)
- 這麼做的話,路徑壓縮就要改一下。
- 我們可以求個後綴異或和。
- 舉例來說,對於 ( 表示y爲x的父親),我們要將1路徑壓縮,即將原樹變成 的形態。若1與2反向,2與3同向,3與4反向,我們倒着來做,求一波後綴和,於是可知3與4反向,2與4反向,1與4同向。
- 還有連邊的時候也要有所修改。
- 對於 ,我們都是讓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即可。
- 時間複雜度: 。
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再連上反向的邊。
- 時間複雜度: 。
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);
}