P4434 题解
一种小常数单
把
考虑转换
我们记一个点
那么对于限制
对于考虑限制
把连通块缩点,得到一个无向图,则有解必须满足该图可以黑白染色。那直接建出来然后 dfs 一遍就好了。若有解则答案即为
后面的两个操作用并查集复杂度
考虑给链上除了顶点的点打上一个标记,若一个点超过一个标记,则将它和它的父亲合并。树上差分 + 并查集即可,复杂度
于是瓶颈在求 lca,精细实现可以优化到
常数非常小,加上快读可以直接拿到(目前的)最优解。
const int N=3e5+5,MOD=1e9+7;
int n,m,u[N],v[N];
const int E=N<<1;
int tot=0,head[N],to[E],nxt[E];
inline void Add(int u,int v){to[++tot]=v;nxt[tot]=head[u];head[u]=tot;}
int f[N]; // 并查集
inline void init(){for(int i=1;i<=n;i++)f[i]=i;}
int find(int x){return x==f[x]?x:f[x]=find(f[x]);}
inline void merge(int u,int v){u=find(u);v=find(v);if(u^v)f[u]=v;}
int fa[N],dep[N],siz[N],son[N],top[N]; // 树剖
void dfs1(int u,int fath){
fa[u]=fath;dep[u]=dep[fath]+1;siz[u]=1;
for(int i=head[u];i;i=nxt[i]){
int v=to[i];if(v==fath)continue;
dfs1(v,u);siz[u]+=siz[v];(siz[v]>siz[son[u]])&&(son[u]=v);
}
}
void dfs2(int u,int tp){
top[u]=tp;if(!son[u])return;dfs2(son[u],tp);
for(int i=head[u];i;i=nxt[i]){int v=to[i];if(v^son[u]&&v^fa[u])dfs2(v,v);}
}
inline int LCA(int u,int v){for(;top[u]^top[v];u=fa[top[u]])if(dep[top[u]]<dep[top[v]])swap(u,v);return dep[u]<dep[v]?u:v;}
inline int gson(int u,int v){
int lst=0;
for(;top[v]^top[u];v=fa[top[v]])lst=top[v];
return u==v?lst:son[u];
}
bool vis[N],col[N];
void dfs(int u){ // 黑白染色
vis[u]=true;
for(int i=head[u];i;i=nxt[i]){
int v=to[i];if(vis[v]){if(col[u]==col[v]){puts("0");exit(0);};continue;}
col[v]=!col[u];dfs(v);
}
}
bool fl[N];int d[N];
void dfs(int u,int fath){
for(int i=head[u];i;i=nxt[i]){
int v=to[i];if(v==fath)continue;
dfs(v,u);d[u]+=d[v];
}
if(d[u])merge(fath,u); // 若有标记则合并
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1,u,v;i<n;i++){scanf("%d%d",&u,&v);Add(u,v);Add(v,u);}
dfs1(1,0);dfs2(1,1);init();
for(int i=1;i<=m;i++){
scanf("%d%d",&u[i],&v[i]);
int l=LCA(u[i],v[i]);
if(u[i]^l){d[u[i]]++;d[gson(l,u[i])]--;}
if(v[i]^l){d[v[i]]++;d[gson(l,v[i])]--;}
if(u[i]==l||v[i]==l){fl[i]=true;continue;}// 注意特判 lca=u 或 lca=v 的情况
}
dfs(1,0);
tot=0;memset(head,0,sizeof head);
for(int i=1;i<=m;i++){
u[i]=find(u[i]);v[i]=find(v[i]);
if(fl[i])continue;
if(u[i]^v[i]){Add(u[i],v[i]);Add(v[i],u[i]);}
else{puts("0");return 0;} // 若有 u,v 不同的限制,u,v 却在一个块内
}
int res=1;
for(int i=2;i<=n;i++)if(f[i]==i&&!vis[i]){
dfs(i);res=(res<<1)%MOD; // 找到一个联通块,答案乘2
}
printf("%d\n",res);
return 0;
}