给定你n张骨牌,每张牌左右两端有一个数字,每张牌的左右两端数字可以颠倒,找出一种摆放骨牌的顺序,使得相邻骨牌的两端数字相同(最左边骨牌的最左端和最右边骨牌的最右端可以不管)。
最先想到把每个数字缩成点,然后发现这样连边都有问题,于是翻了翻各种题解,大概是这样的:把每个骨牌缩成两条边,分别对应正反两种情况,在数字之间连线,找出一种能够遍历所有骨牌的方案,然后就变成了一笔画问题。正向遍历骨牌的时候为+,反向遍历骨牌的时候为-,不管是正向还是反向遍历了这张骨牌,那么就视为这张骨牌已经遍历过了。
如样例:
5
1 2
2 4
2 4
6 4
2 1
这样连边:
如果有骨牌没有被遍历到,那么无解。
这一题我还有一点没有弄明白的地方,就是在保存路径的时候要先dfs下一条边,再保存当前遍历到的边。如果先保存当前遍历的边,再dfs下一条边,就WA了。
#include<cstdio>
#include<cstring>
using namespace std;
inline int mabs(int x)
{
if(x > 0) return x;
else return -x;
}
struct T
{
int v,next,id;
}edge[250];
int head[250],cnt;
void add_edge(int u,int v,int id)
{
edge[cnt].v = v;
edge[cnt].id = id;
edge[cnt].next = head[u];
head[u] = cnt++;
}
int ans[250],tot,du[250];
bool vis[205];
void dfs(int u)
{
for(int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].v;
int id = edge[i].id;
if(!vis[mabs(id)])
{
vis[mabs(id)] = 1;
dfs(v);//先dfs下一条边,再保存当前访问的边
ans[++tot] = id;//
}
}
}
int n,a,b,s = -1;
bool check()
{
int count = 0;
for(int i = 0; i <= 6; i++)
if(du[i]&1)
{
count++;
s = i;
}
if(count == 1||count > 2) return false;
if(count == 2) return true;
for(int i = 0; i <= 6; i++)
if(du[i] > 0)
{
s = i;
break;
}
return true;
}
int main()
{
memset(head,-1,sizeof head);
scanf("%d",&n);
for(int i = 1; i <= n; i++)
{
scanf("%d%d",&a,&b);
add_edge(a,b,i);
add_edge(b,a,-i);
du[a]++;
du[b]++;
}
if(!check())
{
printf("No solution\n");
return 0;
}
dfs(s);
for(int i = 1; i <= n; i++)
if(!vis[i])
{
printf("No solution\n");
return 0;
}
for(int i = tot; i >= 1; i--)//
if(ans[i] > 0)
printf("%d +\n",ans[i]);
else
printf("%d -\n",-ans[i]);
}