在题目中在慢慢细说概念
1.HDU - 3018 Ant Trip
题目大意:又N个村庄,M条道路,问需要走几次才能将所有的路遍历
解题思路:这题问的是有关欧拉路的判定
欧拉路就是每条边只能走一次,且要遍历所有的边,简单的说就是一笔画(图连通)
这道题是无向图的欧拉路,无向图的欧拉路的判定:所有点的度数都是偶数度,或者只有两个点的度是奇数度,且图要是连通图
知道欧拉路是什么后,这题就比较好做了,第一件事就是找到有几个连通块,然后再判断一下每个连通块需要几笔才能完成就好了
#include
#include
#include
#include
using namespace std;
#define N 100010
int node[N];
int n, m;
int p[N];
int vis[N], vis2[N];
int num[N];
map<int,int> M;
int find(int x) {
return x == p[x] ? x : p[x] = find(p[x]);
}
void init() {
M.clear();
memset(node, 0, sizeof(node));
memset(vis, 0, sizeof(vis));
memset(num, 0, sizeof(num));
memset(vis2, 0, sizeof(vis2));
int x, y, px, py;
for (int i = 0; i <= n; i++)
p[i] = i;
for (int i = 0; i < m; i++) {
scanf("%d%d", &x, &y);
px = find(x);
py = find(y);
if (px != py) {
p[px] = py;
}
node[x]++;
node[y]++;
vis[x] = 1;
vis[y] = 1;
}
}
void solve() {
int cnt = 0;
for (int i = 0; i <= n; i++) {
if (vis[i]) {
int x = find(i);
if(!vis2[x]) {
vis2[x] = 1;
M[x] = cnt++;
}
}
}
for (int i = 0; i <= n; i++) {
if (vis[i] && node[i] % 2) {
int x = find(i);
num[M[x]]++;
}
}
int ans = cnt;
for(int i = 0; i < cnt; i++)
if(num[i])
ans += ((num[i] - 1) / 2);
printf("%d\n", ans);
}
int main() {
while (scanf("%d%d", &n, &m) != EOF) {
init();
solve();
}
return 0;
}
2.POJ - 1386 Play on Words
题目大意:问所有的单词是否能首尾相连
解题思路:这题也是求欧拉路的,只不过这题是有向的
有向图的欧拉路判定:首先,要是连通的。接着就分成两种了,一种是所有点的入度等于出度,另一种是只有两个点的入度不等于出度,且其中一个点的入度比出度大一,另一个点的出度比入度大一
#include
#include
#include
#define N 30
#define M 1010
int in[N], out[N], n;
char str[M];
int p[N], vis[N];
int find(int x) {
return x == p[x] ? x : p[x] = find(p[x]);
}
void init() {
scanf("%d", &n);
for (int i = 0; i < N; i++)
vis[i] = out[i] = in[i] = 0;
for (int i = 0; i < N; i++)
p[i] = i;
int len, x, y, px, py;
for (int i = 0; i < n; i++) {
scanf("%s", str);
len = strlen(str);
x = str[0] - 'a';
y = str[len - 1] - 'a';
in[x]++;
out[y]++;
vis[x] = 1;
vis[y] = 1;
px = find(x);
py = find(y);
if(px != py) {
p[px] = py;
}
}
}
bool connect() {
int start = 0;
for (; !vis[start]; start++);
for (int i = start + 1; i < N; i++) {
if (vis[i] && find(i) != find(start)) {
return false;
}
}
return true;
}
void solve() {
int cnt = 0;
bool mark = false;
bool flag1 = false, flag2 = false;
for (int i = 0; i < N; i++) {
if (in[i] - out[i] != 0) {
cnt++;
if (cnt >= 3)
break;
if (abs(in[i] - out[i]) != 1) {
mark = true;
break;
}
if (in[i] - out[i] == -1)
flag1 = true;
if (in[i] - out[i] == 1)
flag2 = true;
}
}
if(mark || cnt >= 3) {
printf("The door cannot be opened.\n");
return ;
}
bool flag3 = connect();
if (cnt == 0 && flag3) {
printf("Ordering is possible.\n");
}
else if (cnt == 2 && flag1 && flag2 && flag3) {
printf("Ordering is possible.\n");
}
else {
printf("The door cannot be opened.\n");
}
}
int main() {
int test;
scanf("%d", &test);
while (test--) {
init();
solve();
}
return 0;
}
以下的题目都要用到Fluery算法
3.POJ - 1041 John’s trip
题目大意:有n个城市,m条路,问能否走遍所有的路,最后又回到起点
解题思路:问能否形成欧拉回路,并要求输出路径的题目
首先是欧拉回路的判定,对无向图来说,所有的点的度数都为偶数就是欧拉回路了,对有向图来说,是所有的点的入度等于出度
其实上面的判定我少加了一个条件,那就是图连通
这题是无向图的,所以判定起来比较简单,关键是如何输出路径了
其实这题是很坑的,所要求的输出最小字典序,其实并不需要,只需要常规的输出就可以了
其实这一题也不需要了解fluery算法,只需要dfs输出就可以了,dfs可以保证路线连通,在设置一个标记就可以输出全部路径了
#include
#include
#include
#include
#include
using namespace std;
#define N 2000
#define M 50
struct Edgs{
int junc, street;
Edgs(int s = 0, int j = 0) : junc(j), street(s) {}
};
int degrees[M];
vector E[N];
stack<int> stk;
bool vis[N];
int Max, x, y, z;
void Euler(int u) {
for (int i = 0; i < E[u].size(); i++) {
if (!vis[E[u][i].street]) {
vis[E[u][i].street] = true;
Euler(E[u][i].junc);
stk.push(E[u][i].street);
}
}
}
void solve() {
for (int i = 0; i < M; i++) {
if (degrees[i] & 1) {
printf("Round trip does not exist.\n");
return ;
}
}
Euler(Max);
while (!stk.empty()) {
printf("%d ", stk.top());
stk.pop();
}
printf("\n");
}
void init() {
scanf("%d", &z);
Max = max(x, y);
memset(degrees, 0, sizeof(degrees));
memset(vis, 0, sizeof(vis));
for(int i = 0; i < N; i++)
E[i].clear();
E[x].push_back(Edgs(z,y));
E[y].push_back(Edgs(z,x));
degrees[x]++;
degrees[y]++;
while(scanf("%d%d", &x, &y) && x + y) {
scanf("%d", &z);
E[x].push_back(Edgs(z,y));
E[y].push_back(Edgs(z,x));
degrees[x]++;
degrees[y]++;
}
}
int main() {
while (scanf("%d%d", &x, &y) != EOF && x + y) {
init();
solve();
}
return 0;
}
4.POJ - 2230 Watchcow
题目大意:有N个点,M条边,要求每条边都走两次,且最后回到原点,输出走的点
解题思路:每条边走两次,不就相当于把一条边变成两条边吗,那就变成两条边再去遍历就可以了
#include
#include
#include
using namespace std;
#define N 10010
vector<int> Edgs[N];
int n, m;
int vis[N];
void init() {
for (int i = 1; i <= n; i++)
Edgs[i].clear();
int x, y;
for (int i = 0; i < m; i++) {
scanf("%d%d", &x, &y);
Edgs[x].push_back(y);
Edgs[y].push_back(x);
}
}
void Euler(int u) {
int tmp;
for (int i = 0; i < Edgs[u].size(); i++) {
tmp = Edgs[u][i];
if(!tmp)
continue;
Edgs[u][i] = 0;
Euler(tmp);
}
printf("%d\n", u);
}
int main() {
while (scanf("%d%d", &n, &m) != EOF ) {
init();
Euler(1);
}
return 0;
}
5.POJ - 2404 Jogging Trails
插播一题欧拉回路+floyd+状态压缩的题目
题目大意:有N个点,M条路,每条路都有相应的权值。现在有一个人想把所有的路都跑遍,最后又回到原点,问最小权值是多少
解题思路:跑欧拉回路的话,肯定能使权值到达最小。
所以问题就变成了,通过增边将M条路构造成欧拉回路
问题进一步变成了如何增边才能使权值达到最低,这就需要用到状态压缩dp了
#include
#include
#include
#include
using namespace std;
#define N 20
#define INF 0x3f3f3f3f
#define S (1<<15)
int n, m, ans;
int dis[N][N], degrees[N], dp[S];
void floyd() {
for (int k = 0; k < n; k++)
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if(dis[i][k] != INF && dis[k][j] != INF && dis[i][j] > dis[i][k] + dis[k][j])
dis[i][j] = dis[i][k] + dis[k][j];
}
int dfs(int state) {
if (state == 0) {
return 0;
}
if (dp[state] != -1) {
return dp[state];
}
dp[state] = INF;
for (int i = 0; i < n; i++) {
if (state & (1 << i)) {
for (int j = 0; j < n; j++) {
if ((state & (1 << j)) && i != j) {
dp[state] = min(dp[state], dfs(state ^ (1 << i) ^ (1 << j)) + dis[i][j]);
}
}
}
}
return dp[state];
}
void solve() {
int state = 0;
for (int i = 0; i < n; i++) {
if (degrees[i] % 2) {
state |= (1 << i);
}
}
memset(dp, -1,sizeof(dp));
ans += dfs(state);
printf("%d\n", ans);
}
void init() {
ans = 0;
memset(dis, 0x3f, sizeof(dis));
for (int i = 0; i < N; i++)
degrees[i] = 0;
for (int i = 0; i < n; i++)
dis[i][i] = 0;
int x, y, z;
scanf("%d", &m);
for (int i = 0; i < m; i++) {
scanf("%d%d%d",&x, &y, &z);
dis[x - 1][y - 1] = dis[y - 1][x - 1] = min(dis[x - 1][y - 1], z);
ans += z;
degrees[x - 1]++;
degrees[y - 1]++;
}
floyd();
}
int main() {
while (scanf("%d", &n) != EOF && n) {
init();
solve();
}
return 0;
}
6.POJ - 2337 Catenyms
题目大意:这题就比较有难度了,问能否将字符串头尾相连,如果可以的话,输出字典序最小的字符串
解题思路:参考了学长的。有向欧拉路的判断就不说了,关键是如何输出字典需最小的
首先,先给所有的字符串排序
按照fleury算法,输出时依据栈里的内容输出,而栈是从顶到底输出的,也就是说,压栈的时候,先将字典序大的压栈即可
#include
#include
#include
#include
#include
#include
using namespace std;
#define N 30
#define M 1010
vector<int> v[N];
int vis[M], p[N], in[N], out[N];
char word[M][N];
int n;
stack<int> stk;
int find(int x) {
return x == p[x] ? x : p[x] = find(p[x]);
}
void output() {
while (!stk.empty()) {
printf("%s", word[stk.top()]);
stk.pop();
if (!stk.empty()) {
printf(".");
}
}
}
int cmp (const void *a, const void *b) {
return strcmp((char *)a, (char *)b);
}
void push (int u, int v) {
for (int i = n - 1; i >= 0; i--) {
if(!vis[i] && word[i][0] - 'a' == u && word[i][strlen(word[i]) - 1] - 'a' == v) {
stk.push(i);
vis[i] = 1;
return ;
}
}
}
void dfs(int u) {
while (!v[u].empty()) {
int tmp = *(v[u].begin());
v[u].erase(v[u].begin());
dfs(tmp);
push(u, tmp);
}
}
void solve() {
int root;
for (int i = 0; i < N; i++) {
if (vis[i]) {
root = find(i);
break;
}
}
for (int i = 0; i < N; i++) {
if (vis[i] && find(i) != root) {
printf("***");
return ;
}
}
int start = 0, cnt;
for (int i = 0; i < N; i++) {
if (abs(in[i] - out[i]) > 1) {
printf("***");
return ;
}
if (abs(in[i] - out[i]) == 1) {
cnt++;
if(in[i] - out[i] == 1)
start = i;
}
}
if(cnt > 2) {
printf("***");
return ;
}
if(!cnt)
start = word[0][0] - 'a';
memset(vis, 0, sizeof(vis));
dfs(start);
output();
}
void init() {
scanf("%d", &n);
for (int i = 0; i < n; i++)
scanf("%s", word[i]);
qsort(word, n, sizeof(word[0]), cmp);
memset(vis, 0, sizeof(vis));
for (int i = 0; i < N; i++) {
v[i].clear();
in[i] = out[i] = 0;
p[i] = i;
}
int x, y;
for (int i = 0; i < n; i++) {
x = word[i][0] - 'a';
y = word[i][strlen(word[i]) - 1] - 'a';
v[x].push_back(y);
vis[x] = vis[y] = 1;
in[x]++;
out[y]++;
p[find(x)] = find(y);
}
}
int main() {
int test;
scanf("%d", &test);
while(test--) {
init();
solve();
printf("\n");
}
return 0;
}
接下来的题目,算是欧拉路的应用吧
7.UVA - 10040 Ouroboros Snake
题目大意:有个轮子上面有2 * n个数字,有n个数字是0,n个数字是1,这个轮子很神奇,在纸上滚动时,会将轮子上面的0,1印在纸张上,仔细看纸张上面的数字,从头开始,分别从第1个到第2^n个截取出长度为n的二进制数,会发现这些二进制数包含了0—-(1 << n)-1的所有数
现在要求你按规则输出第k个开头,长度为n的二进制数
解题思路:因为包含了所有0–(1 << n)-1的数,这些长度为n的二进制数都是0-1构成的,且所有的出度等于入度,所以这就形成了一个欧拉回路了
因此在构造这个轮子在纸张上打印的字时可以按照输出欧拉回路的方法构造,
具体的可以看这篇文章,里面有对代码的详细解说代码详解
#include
#include
#include
using namespace std;
#define N 16
#define S (1<<16)
int pow2[N];
bool vis[S][2];
int ans[S];
int cnt, n, k;
void erule(int s) {
for (int i = 0; i < 2; i++) {
if (!vis[s][i]) {
vis[s][i] = true;
erule(((s << 1) + i) % pow2[n - 1]);
ans[cnt++] = i;
}
}
}
int main() {
pow2[0] = 1;
for (int i = 1; i < N; i++)
pow2[i] = pow2[i - 1] * 2;
while (scanf("%d%d", &n, &k) != EOF && n + k) {
memset(vis, 0, sizeof(vis));
memset(ans, 0, sizeof(ans));
cnt = 0;
erule(0);
cnt += n - 2 - k;
int t = 0;
for (int i = 0; i < n; i++)
t = t * 2 + ans[cnt--];
printf("%d\n", t);
}
return 0;
}
8.HDU - 2894 DeBruijin
这题跟上一题类似
#include
#include
#include
using namespace std;
#define N 16
#define S (1<<16)
int pow2[N];
bool vis[S][2];
int ans[S];
int cnt, n, k;
void erule(int s) {
for (int i = 0; i < 2; i++) {
if (!vis[s][i]) {
vis[s][i] = true;
erule(((s << 1) + i) % pow2[n - 1]);
ans[cnt++] = i;
}
}
}
int main() {
pow2[0] = 1;
for (int i = 1; i < N; i++)
pow2[i] = pow2[i - 1] * 2;
while (scanf("%d", &n) != EOF && n) {
memset(vis, 0, sizeof(vis));
memset(ans, 0, sizeof(ans));
cnt = 0;
erule(0);
printf("%d ", pow2[n]);
cnt += n - 2;
for (int i = pow2[n]; i > 0; i--)
printf("%d",ans[cnt--]);
printf("\n");
}
return 0;
}
9.POJ - 1780 Code
这题的话,我就当个搬运工,附上大神的详细解说
详细解说
#include
#include
using namespace std;
#define N 1000010
char ans[N];
bool vis[N];
int pow10[8];
int n;
void solve() {
if(n == 1) {
printf("0123456789\n");
return ;
}
memset(vis, 0, sizeof(vis));
for (int i = 0; i < n; i++)
ans[i] = '0';
int cur = n, now = 0, i = 0;
vis[0] = true;
while (cur < pow10[n] + n - 1) {
now %= pow10[n - 1];
for (; i < 10; i++) {
if (!vis[now * 10 + i]) {
vis[now * 10 + i] = true;
now = now * 10 + i;
ans[cur++] = i + '0';
i = 0;
break;
}
}
if (i == 10 && cur < pow10[n] + n - 1) {
cur--;
now = (ans[cur - n + 1] - '0') * pow10[n - 1] + now;
vis[now] = false;
i = now % 10 + 1;
now /= 10;
}
}
ans[cur] = '\0';
printf("%s\n", ans);
}
void init() {
pow10[0] = 1;
for (int i = 1; i < 8; i++)
pow10[i] = pow10[i - 1] * 10;
}
int main(){
init();
while (scanf("%d", &n) != EOF && n) {
solve();
}
return 0;
}