CF840B Leha and another game about graph
首先给出一个结论:给定一个连通图,要求构造一个给定每个点度数奇偶性的图(不要求连通,但新图的边集是原图边集的子集)。若奇点个数为奇数则无解;反之则一定有解。
无解非常好理解:因为图的所有点的总的度数一定为偶数,所以奇点个数显然不可能为奇数。
下面将通过构造的方式证明后者一定有解。
如何同时改变两个点的奇偶性:由于原图是连通的,我们只需要找到一条这两点之间的路径并翻转即可(翻转的意思是本来选改为不选,不选则改为选)。路径中其他点显然不改变奇偶性,而两端的奇偶性改变,这样我们就达到了目的。
我们假设一开始一条边都不选,那么没有奇点。由于要求奇点个数是偶数,所以我们可以把奇点随机两两配对,每次寻找一条路径操作来获得两个奇点(上面的方法),这样的构造一定可以满足要求。
下面给出一个具体的操作流程:
- 随便找一棵原图的生成树
- 以一个节点为根,计算每个子树内奇点个数
- 若 \(x\) 子树内奇点个数为偶数,则不选 \(x\) 到 \(fa_x\) 这条边,反之必须选
其中 \(fa_x\) 表示 \(x\) 的父亲节点。
关于3.的解释是,若子树内要求的奇点是奇数个,则显然无法通过子树内部的匹配来解决这些奇点,必然会有奇数个点要向子树外连边,所以 \(x \to fa_x\) 这条边一定被选择了奇数次,所以最后呈现出来的是必须要选。
对于这道题增加了度数可奇可偶的一种点。显然,如果有这种点那么一定有解,我们可以在保证奇点个数为偶数的前提下随便安排这些点。若没有这种点,则需要通过奇点个数来判断是否有解。剩下的就直接按照上面的方法做即可。
代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=300009;
int head[N],cnt,n,m,deg[N],flag,all,fa[N],x[N],y[N],ans[N],siz[N],Ans;
struct Edge
{
int nxt,to,w;
}g[N*2];
void add(int from,int to,int w)
{
g[++cnt].nxt=head[from];
g[cnt].to=to;
g[cnt].w=w;
head[from]=cnt;
}
void init()
{
scanf("%d %d",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%d",°[i]);
if(deg[i]==-1) flag=1;
else all+=deg[i];
fa[i]=i;
}
for (int i=1;i<=m;i++)
scanf("%d %d",&x[i],&y[i]);
}
int find(int x) { return x==fa[x]?x:fa[x]=find(fa[x]); }
void dfs(int x,int fa,int k)
{
siz[x]=deg[x];
for (int i=head[x];i;i=g[i].nxt)
{
int v=g[i].to;
if(v==fa)
continue;
dfs(v,x,g[i].w);
siz[x]+=siz[v];
}
if(siz[x]&1)
ans[k]=1;
}
void work()
{
if(!flag&&(all&1))
{
puts("-1");
return;
}
for (int i=1;i<=n;i++)
if(deg[i]==-1)
if(all&1)
all++,deg[i]=1;
else
deg[i]=0;
for (int i=1;i<=m;i++)
{
int A=find(x[i]),B=find(y[i]);
if(A!=B)
fa[B]=A,add(x[i],y[i],i),add(y[i],x[i],i);
}
for (int i=head[1];i;i=g[i].nxt)
dfs(g[i].to,1,g[i].w);
for (int i=1;i<=m;i++)
Ans+=ans[i];
printf("%d\n",Ans);
for (int i=1;i<=m;i++)
if(ans[i])
printf("%d\n",i);
}
int main()
{
init();
work();
return 0;
}
由于博主比较菜,所以有很多东西待学习,大部分文章会持续更新,另外如果有出错或者不周之处,欢迎大家在评论中指出!