POI 2008 CLO-Toll
POI 2008 CLO-Toll
题意翻译
题目描述:
给你 nn 个点和 mm 条双向边,问能否将其中的一些边改成有向边,使得只考虑有向边的情况下每个点的入度都为 11 。
输入格式:
第一行输入 n,m(1≤n≤100000,1≤m≤200000)n,m(1≤n≤100000,1≤m≤200000) ,接下来 mm 行每行两个数 a,ba,b 表示点 aa 和点 bb 之间有一条双向边。输入保证没有重边与自环。
输出格式:
若没有合法方案,输出 NIENIE ,否则先在第一行输出 TAKTAK ,然后在第 i+1i+1 行输出点 ii 的入度是由哪个点出发的边所得到的。
感谢@hdxrie 提供的翻译
题解:
其实很简单的。就是在每个连通块里拎出来基环树。
如果拎不出连通块,那就是NIE。
然后考虑怎么拎基环树。
基环树是啥啊,就是一棵树加上任意一条边呗。这条边就是“返祖边”啊。所以我们只需要对每个连通块DFS一遍,只拎一条返祖边(这个用flag维护),碰到返祖边,就说明可以和一些节点构成一个环,这样的话就回溯,把所有沿途节点全部反向即可。
代码:
#include<cstdio>
using namespace std;
const int maxn=1e5+5;
const int maxm=2e5+5;
int n,m;
int tot,head[maxn],nxt[maxm<<1],to[maxm<<1];
int fa[maxn],ans[maxn];
bool v[maxn];
bool flag;
void add(int x,int y)
{
to[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
void dfsback(int x,int s)
{
if(ans[x])
dfsback(ans[x],x);
ans[x]=s;
}
void dfs(int x)
{
v[x]=1;
for(int i=head[x];i;i=nxt[i])
{
int y=to[i];
if(y==fa[x])
continue;
if(v[y])
{
if(!flag)
{
flag=1;
dfsback(y,x);
}
}
else
{
ans[y]=fa[y]=x;
dfs(y);
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y);
add(y,x);
}
for(int i=1;i<=n;i++)
if(!v[i])
{
flag=0;
dfs(i);
if(!flag)
{
puts("NIE");
return 0;
}
}
puts("TAK");
for(int i=1;i<=n;i++)
printf("%d\n",ans[i]);
return 0;
}