题目大意:一个旋转鼓,上面均匀分成m份,每一份可以表示二进制的0或1,现在求一个最短的01序列,使任意的k个相邻的01序列表示不同的值。并输出字典序最小的一个序列。
题目分析:这个序列是循环的,长度为k的二进制数表示的范围是0-2^k - 1,鼓转动一小格,当前k个二进制位与前一个状态相比,只有首尾2个二进制位发生了改变,中间的k-1位是不变的,考虑以这k-1位二进制位为点,建图,每个点伸出2条边,每条边的权值就是该点末尾补0 或1得到的值。举例说明一下,当k = 3的时候,有00,01,10,11,4种中间状态,就以这4个状态为点建图,00后面补1变成001,去掉高位后变成01,到达01这个状态,这条边的权值就是001,01后面补1变成011,到达11状态,这条边权值为011,同理,可以建2^3=8条边,可以发现这8条边各不相同,正好能表示0-2^k-1这2^k个数。于是这题就变成了求欧拉回路的问题。以2^(k - 1)个中间状态为点建图,建2^k条边,遍历这个图求一条欧拉回路即可。
详情请见代码:
#include <iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1<<12;
struct node
{
int to;//边的右端点
int value;//这条边的值
int next;//同一个左端点的下一条边
bool vis;//
}edge[N];
int head[N];
int ans,len,num,k;
int stack[N],top;
void init(int l)
{
int i;
for(i = 0;i < l;i ++)
head[i] = -1;
num = top = 0;
}
void build(int s,int e,int v)
{
edge[num].to = e;
edge[num].value = v;
edge[num].vis = 0;
edge[num].next = head[s];//后建的边往前插
head[s] = num ++;
}
void dfs(int u)
{
int i;
for(i = head[u];i != -1;i = edge[i].next)
{
if(!edge[i].vis)
{
edge[i].vis = 1;
dfs(edge[i].to);
stack[top ++] = edge[i].value/len;
//printf("%d",edge[i].value/len);
}
}
}
int main()
{
int i;
while(scanf("%d",&k) != EOF)
{
ans = 1<<k;
len = 1<<(k - 1);
init(len);
for(i = 0;i < len;i ++)
{
int id = i<<1|1;//先建1边,再建0边
build(i,id%len,id);//dfs的时候先搜
id = i<<1;//权值小的边,保证字典序最小
build(i,id%len,id);
}
printf("%d ",ans);
dfs(0);
for(i = top - 1;i >= 0;i --)
printf("%d",stack[i]);
printf("\n");
}
return 0;
}
//31MS 384K