题目
题目链接:https://codeforces.com/problemset/problem/453/C
给一个无向图,n个点m条边,给定一个01序列,如果a[i]=1,要求走到这个点奇数次,否则,要求走到这个点偶数次,请你任选起点,输出满足要求的经过点的序列和序列长度,序列长度不能超过4n。
\(n,m\leq 10^5\)。
思路
如果有多张连通图且至少有两张连通图中都有要求经过次数为奇数的点显然无解。
否则必然有解。
我们把经过次数为奇数定义为黑色,经过次数为偶数为白色。我们的目标是把所有节点全部变为白色。
随便找一棵生成树,按照 dfs 序染色。假设 \(x\) 的子树内所有点都已经变为白色了,那么返回 \(x\) 父亲 \(y\) 的时候,判断如果 \(x\) 是黑色,就再次经过 \(y\to x\to y\) 这两条边把 \(x,y\) 反色。
这样最终根节点肯能是黑色或者白色,如果是黑色,那么我们最后回到根节点的一步就不走就行了。
时间复杂度 \(O(n+m)\)。不考虑 \(x\to y\to x\) 的路径的话,一个节点 \(x\) 会被经过的次数是其儿子数量 \(+1\),然后 \(x\to y\to x\) 的路径最多只会走 \(m\) 次,所以路径长度上界是 \(\sum_{x\in T}\text{deg}_x+2m\leq 4n\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=100010;
int n,m,tot,head[N],father[N],col[N];
bool flag,vis[N],vis2[N];
stack<int> st;
struct edge
{
int next,to;
}e[N*2];
void add(int from,int to)
{
e[++tot]=(edge){head[from],to};
head[from]=tot;
}
bool dfs1(int x,int fa)
{
if (col[x]) return 1;
if (vis2[x]) return 0;
vis2[x]=1;
for (int i=head[x];~i;i=e[i].next)
if (e[i].to!=fa) dfs1(e[i].to,x);
return 0;
}
void dfs2(int x,int fa)
{
st.push(x);
vis[x]=1; col[x]^=1;
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (v!=fa && !vis[v])
{
dfs2(v,x);
st.push(x); col[x]^=1;
if (col[v])
{
st.push(v); st.push(x);
col[v]^=1; col[x]^=1;
}
}
}
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for (int i=1,x,y;i<=m;i++)
{
scanf("%d%d",&x,&y);
add(x,y); add(y,x);
}
for (int i=1;i<=n;i++)
scanf("%d",&col[i]);
for (int i=1;i<=n;i++)
if (!vis[i])
{
int s=dfs1(i,0);
if (flag && s) return printf("-1"),0;
flag|=s;
if (s) dfs2(i,0);
if (col[i]) st.pop();
}
printf("%d\n",st.size());
for (;st.size();st.pop()) printf("%d ",st.top());
return 0;
}