Files
python/TangDou/AcWing/EulerPath/HihoCoder1182_1.cpp
HuangHai 1f397eca87 'commit'
2025-08-30 18:35:01 +08:00

123 lines
7.0 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <bits/stdc++.h>
using namespace std;
/* 举栗子:以下面的小方框个数=3为例,其实是模拟了4个点,8条边,也就是(00,01,10,11)->通过尾部加0或加1->构建边->(000,001,010,011,100,101,110,111)-> 去掉头部第1个->(00,01,10,11)
其中 4=2^2=2(3-1),8=2^3,推广一下,如果下面的小方框个数是n,点数2^(n-1),边是2^n=[0000...0,1111...1]条
题目中要求1<=N<=15,那么我们加大一点范围,就让N=16,但是,由于此时N的含义是边的个数,也就是M=N*2,下面的代码,含糊了N和M的概念,将其都设定为1<<16,这样足够大,并且不超过题目要求
*/
const int N = 1 << 16, M = N;
int len;
int res[N], rl; // 路径记录器
int st[N]; // 某条边是不是出现过了
// 链式前向星
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c = 0) {
e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
// dfs求有向图欧拉回路
void dfs(int u) {
for (int i = h[u]; ~i; i = h[u]) {
h[u] = ne[i]; // 套路式删边,优化性能
int id = w[i];
if (st[id]) continue; // 如果id这条边访问过了,那么不需要重复访问
st[id] = 1; // 标记i号边已访问过
dfs(e[i]); // 递归搜索v号点
// cout << "u=" << u << ",v=" << v << ","
// << "w[i]=" << w[i] << endl;
res[++rl] = id; // 以边的形式记录欧拉路径,i号索引的这条边,它的真实边号为w[i]
}
}
int main() {
#ifndef ONLINE_JUDGE
freopen("HihoCoder1182.in", "r", stdin);
#endif
int n;
scanf("%d", &n);
memset(h, -1, sizeof h);
/*
len的第一层含义
一、现在点是10(二进制表示),对应十进制是就是2号点,现在需要它扩展出边来,当然就是100,101
二、那么,这两条边走向了哪两个节点呢?
(1)10->加0->生成边-> 100 ->哪个节点?
(2)10->加1->生成边-> 101 ->哪个节点?
其实用人眼看是走到了00和01两个点,也就是去掉最前面的那个数字1就行。
那如何让计算机知道它们走向了哪两个点呢?办法就是取模!就是将边对应的数字对4取模,则最前面的二进制1将被裁掉,只保留最后两位的00和01
那么,这个4是怎么来的呢?其实就是 1<< (n-1)!
总结:我们只要对生成的边对应的十进制数字,对 len= 1<<(n-1) 取模,就可以获得到它走向了哪个点ID
*/
len = 1 << (n - 1);
// len的第二层含义:如果点号从0开始计数,那么,最大的点号也是 1<<(n-1),以n=3为例,四个点就是(0,1,2,3),最大点号 3= (1<<(3-1)) -1
for (int a = 0; a < len; a++) {
int c = a << 1 | 1; // 点号,左移一位,尾部添1,以10为例,就是变成 100+1=101,这是一个点到边的变换过程,num是指边号对应的十进制数,比如101=5
int b = c % len; // 再模一下我们精心准备的len,就可以把头部第1位裁掉,得到目标节点号
add(a, b, c); // 从a点出发,到b点去,通过一条边号为num的边.我们把链式前向星魔改了一下,把边权重定义为边的边号
c = a << 1; // 点号,左移一位,尾部添0,以10为例,就是变成 100,这是一个点到边的变换过程,num是指边号对应的十进制数,比如100=4
b = c % len; // 再模一下我们精心准备的len,就可以把头部第1位裁掉,得到目标节点号
add(a, b, c); // 从a点出发,到b点去,通过一条边号为num的边.我们把链式前向星魔改了一下,把边权重定义为边的边号
// 上面这段,配置题解中的图进行理解效果更佳!
}
/*
Q:兹鼓欧拉回路,为啥题意这样的东西能和欧拉回路搞在一起,它们之间有什么联系?
答:根据题解中的图,和上面我们的建边,我们可以理解:所有的点都有两条出边,也都有两条入边!也就是它的出度=2,入度=2
图是连通的(二进制的性质保证了没有孤立点),并且,每个点的出度入度都是偶数,都等于2,这恰好是欧拉回路的充要条件!这才是这两个好基友合并的基础!
既然已经确定题目的类型是欧拉回路,那只要我们求出任意一条欧拉回路就可以了,因为0号点是肯定存在的,(当然,你也可以从任何存在的点号出发,本题说任意一条合理的路径就行),我们
从0号点开始搜索,从深搜的dfs(v)后,在循环内记录边即可。
注意:如果不是记录边,而是记录点的话,需要在深搜dfs(v)后,在循环外记录点才可以,这是差别!
*/
dfs(0);
/* 题目要求:输出 1个长度为2^n的01串,表示一种符合要求的分布方案
理解:将这个串把顺序一个一个进入下面的小方框,就可以获取到所有的数字组合。
那与我们已经记录下来的边有什么关系呢?
以本题的样例答案00010111为例进行解释:(下面的小方框是3个)
(1)倒数第1位1下来进入小方框,得到001
(2)倒数第2位1下来进入小方框,得到011
(3)倒数第3位1下来进入小方框,得到111
(4)倒数第4位0下面进入小方框,将最前面的1弹出,得到110
(5)倒数第5位1下面进入小方框,将最前面的1弹出,得到101
(6)倒数第6位0下面进入小方框,将最前面的1弹出,得到010
(7)倒数第7位0下面进入小方框,将最前面的0弹出,得到100
(8)倒数第8位0下面进入小方框,将最前面的0弹出,得到000
将dfs函数中我注释掉的代码放开,可以得到如下的代码运行结果
u=2,v=0,w[i]=4 =(100)
u=3,v=2,w[i]=6 =(110)
u=3,v=3,w[i]=7 =(111)
u=1,v=3,w[i]=3 =(011)
u=2,v=1,w[i]=5 =(101)
u=1,v=2,w[i]=2 =(010)
u=0,v=1,w[i]=1 =(001)
u=0,v=0,w[i]=0 =(000)
参考答案:00010111
此时,你会惊奇的发现,只要把得到的结果倒序,并且每次截取最前面一位的数字,拼装起来就是参考答案!
Q:为什么会有这么神奇的现象呢?
答:因为是队首出列啊,所以,对于每个路径,当然是只取最前面的那一位了!
也就是把序列倒序来看,
000->被顶出去->0
001->被顶出去->0
010->被顶出去->0
101->被顶出去->1
011->被顶出去->0
111->被顶出去->1
110->被顶出去->1
100->被顶出去->1
Q:怎么样能截取最前面的第一位呢?就不再是取模了,而用除法啦!
res[i]/len就是答案!其中len=4,也就是去掉最后两位的意思!
*/
for (int i = rl; i; i--) printf("%d", res[i] / len);
return 0;
}