12558 - Egyptian Fractions (HARD version) (IDA* + 剪枝)

题目链接


https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=4003

题目大意


埃及分数问题,给定一个分数,用几个不相同的分数表示,有多个表示的话,用的分数越少越好,还是多解的话,最小的分数越大越好,然后第二小的分数越大越好……一直到最大的分数越大越好。

解题过程


见紫书分析

题目分析


见紫书分析
代码已加入注释

AC代码


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#include<bits/stdc++.h>
using namespace std;

#define LL long long

LL ans[11234567], tans[11234567];

//book用来标记不可用作分母的数字,题目说小于1000,下面代码用到book的地方都是判断分母是否可用
bool book[11234];

//得到一个结果后,如果比ans更优,那么更新
bool better(int d) {
for (int i = d; i >= 0; i--) {
if (ans[i] != tans[i])
return ans[i] == -1 || tans[i] < ans[i];
}
}

//获得下一个比 a/b 小的 1/c
LL get_next(LL a, LL b) {
for (LL i = 1; ; i++) {
if (i <= 10000 && book[i])
continue;
if (b <= a * i)
return i;
}
}

bool dfs(int max_deep, LL from, LL aa, LL bb, int deep) {
//如果到达最大深度,那么需要判断下,然后return
if (deep == max_deep) {
//判断最后的状态是否为 1/c 的形式,如果不算,那么当前状态不可表示原分数
if (bb % aa)
return false;
if (bb <= 10000 && book[bb/aa])
return false;
tans[deep] = bb/aa;
//如果当前解更优,那么更新ans
if (better(deep))
memcpy(ans, tans, sizeof(LL) * (deep+1));
return true;
}

bool ok = false;

//得到的 1/c 中 c 至少比上一次的分母大才可以 from 即是上一次的分母加一
from = max(from, get_next(aa, bb));
for (int i = from; ; i++) {
if (i <= 10000 && book[i])
continue;

//如果剩下的递归次数,都是用 1/i 还小于 aa/bb 的话,那么当前 i 和比 i 大的数字不能用来做分母
if (bb * (max_deep+1-deep) <= i * aa)
break;
tans[deep] = i;

//计算 aa/bb - 1/i,gcd 用来约分
LL b2 = bb*i;
LL a2 = aa*i - bb;
LL g = __gcd(a2, b2);
if (dfs(max_deep, i+1, a2/g, b2/g, deep+1))
ok = true;
}
return ok;
}

int main() {
int n;
scanf("%d", &n);
for (int cases = 1; cases <= n; cases++) {
memset(book, 0, sizeof(book));
LL a, b, k;
scanf("%lld %lld %lld", &a, &b, &k);
for (int i = 0; i < k; i++) {
LL t;
scanf("%d", &t);
book[t] = true;
}

for (int i = 0; ; i++) {
memset(ans, -1, sizeof(ans));
if (dfs(i,get_next(a, b), a, b, 0))
break;
}

printf("Case %d: %lld/%lld=", cases, a, b);
for (int i = 0; ans[i] != -1; i++) {
if (i)
putchar('+');
printf("1/%lld", ans[i]);
}
putchar('\n');
}
}

690 - Pipeline Scheduling (DFS + 状态压缩 + 剪枝)

题目链接


https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=631

题目大意


有 5 个不同的工作单元,10 个相同的程序,每个程序需要运行 n 个时间段,每个时间段需要一个工作单元工作。现在问至少需要多少时间,才可以执行完全部的程序。

解题过程


大体思路就是暴力模拟,显然可以状态压缩用位运算,然后就是剪枝的问题了。

最开始想的是最暴力的模拟,从移一位开始试,如果可以移动了就 break 掉,这样找的每一步都是最短的,但总体上不是最优的。

然后就去删掉了 break ,循环到移动 n 位。

最后还是超时,又加了如果当前长度加上后面的最少长度大于答案就剪掉,还是超时,不过这里到是用的 IDA* 换了直接 DFS 还是超时。

于是去找了博客……

题目分析


以后做题的时候要考虑下,暴力 dfs 时候,每一个状态到下一个状态的路径个数,可不可以优化,感觉这题的思想类似 KMP 先预处理可以走的步长,避免以后很多次的判断。

  • 有两次剪枝
    • 一是开始就初始化一下,仅有两个任务的情况每次可以移动的步长。这样比直接暴力移动省了不少时间。
    • 二是如果当前还需要运行的程序个数乘最短步长后,如果大于当前的结果,那么剪掉。
  • 每次的状态用一个一位数组储存,每个工作单元用状态压缩一个整数表示。
  • 每次检测好移动用位运算。

    AC代码


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include<bits/stdc++.h>
using namespace std;

const int MAX = 30;

char rawData[6][MAX];
int data[MAX], n, ans, jump[MAX], cnt;

bool judge(int A[], int k) {
for (int i = 0; i < 5; i++) {
if ((A[i]>>k)&data[i])
return false;
}
return true;
}

void codingData(int n) {
ans = n*10, cnt = 0;
memset(data, 0, sizeof(data));
for (int i = 0; i < 5; i++) {
scanf("%s", rawData[i]);
for (int j = 0; j < n; j++) {
if (rawData[i][j] == 'X') {
data[i] |= (1<<j);
}
}
}
for (int i = 0; i <= n; i++) {
if (judge(data, i))
jump[cnt++] = i;
}
}

void attempt(int dep, int A[], int pos) {
if (pos + (10 - dep) * jump[0] > ans)
return;
if (dep == 10) {
ans = min(pos, ans);
return;
}
for (int i = 0; i < cnt; i++) {
if (judge(A, jump[i])) {
int B[MAX] = {};
for (int j = 0; j < 5; j++) {
B[j] = (A[j]>>jump[i]) | data[j];
}
attempt(dep+1, B, pos + jump[i]);
}
}
}

int main() {
while (~scanf("%d", &n) && n) {
codingData(n);
attempt(1, data, n);
printf("%d\n", ans);
}
}

818 - Cutting Chains (枚举子集 + 状态压缩)

题目链接


https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=759

题目大意


给定n个环,其中有些环可以扣在一起,一个环可以和多个环扣在一起。
现在需要求最少打开多少个环才能使这些环构成一条链。(当然打开了环还需要扣上,打开扣上算一次操作)

解题过程


既然是暴力里面的题,自然应该是暴力解决的。然后思路是枚举每一个环,枚举他的所有删边增边情况。大体用 IDA* 搞一搞。
然后超时了,跑到第四层就要好长时间,第五层直接走不动了。
然后就去翻了翻博客,原来是位运算枚举子集。

题目分析


  • 用集合表示每一个环打不打开的情况,首先所有的环至多打开一次。
  • 然后用二进制枚举枚举子集。
  • 统计下每个环的度,如果度大于 2,那么一定构不成链。
  • dfs 一下删除打开的环后,剩下的连通块有没有构成环。
  • 最后以打开的环当做边,看看能否把这些独立的链连起来。如果打开的环数大于等于独立的链的条数减一,即可连起来。

__builtin_popcount() 函数使用

1
2
int n;
__builtin_popcount(n);

函数返回 n 的二进制表示中 1 的个数。
用二进制枚举子集的时候可以用来统计 1 的个数。

AC代码


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include<bits/stdc++.h>
using namespace std;

int g[16][16], gmask[16];
int visited[16];

int dfs(int u, int p, int open, int n) {
visited[u] = 1;
for (int i = 0; i < n; i++) {
if ((open>>i)&1)
continue;
if (g[u][i] == 0 || i == p)
continue;
if (visited[i] || dfs(i, u, open, n))
return 1;
}
return 0;
}

int checkChain(int open, int n) {
for (int i = 0; i < n; i++) {
if ((open>>i)&1)
continue;
int t = gmask[i]^(gmask[i]&open);
int degree = __builtin_popcount(t);
if (degree > 2)
return 0;
}

int op = __builtin_popcount(open);
int comp = 0;
memset(visited, 0, sizeof(visited));
for (int i = 0; i < n; i++) {
if ((open>>i)&1)
continue;
if (visited[i])
continue;
if (dfs(i, -1, open, n))
return 0;
comp++;
}
return op >= comp - 1;
}

int main() {
int n, cases = 0;
while (~scanf("%d", &n) && n) {
memset(g, 0, sizeof(g));
memset(gmask, 0, sizeof(gmask));
while (true) {
int a, b;
scanf("%d %d", &a, &b);
if (a == -1 && b == -1)
break;
a--, b--;
g[a][b] = g[b][a] = 1;
gmask[a] |= 1<<b, gmask[b] |= 1<<a;
}

int ret = 0x3f3f3f3f;
for (int i = 0; i < (1<<n); i++) {
int op = __builtin_popcount(i);
if (op >= ret)
continue;
if (checkChain(i, n))
ret = min(ret, op);
}
printf("Set %d: Minimum links to open is %d\n", ++cases, ret);
}
}

计算行列式(高斯消元?+Java+工具)

功能:


计算行列式并输出

用法:


首先输入行列式的阶数,然后以输入行列式内容。

例如:

输入:
4
1 2 -1 3
2 3 -1 2
-1 1 1 0
0 1 -2 1

输入:
18.0

实现:


好像是高斯消元,就是每一行乘一个系数减下去,化三角。
时间复杂度 O(n^3) .

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import java.util.*;
import java.lang.*;

public class Determinant {

public static void main(String[] Args){
Scanner cin = new Scanner(System.in);
int size = cin.nextInt();
double[][] data = new double[size][size];

//读取数据
for (int i = 0; i < size; i++) {
for (int j = 0; j < size; j++) {
data[i][j] = cin.nextDouble();
}
}

//标记是否可以提前确定行列式为0
boolean zero = false;
//记录交换行后的正负号
double sign = 1;

//主体
for (int row = 0; row < size-1; row++){

//如果第i行第i个元素已经是0的话,就去找一行非零的行交换
if (data[row][row] == 0) {
boolean flag = false;
for (int trow = row + 1; trow < size; trow++){
if (data[trow][row] != 0){
flag = true;
swap_row(row, trow, size, data);
sign *= -1;
break;
}
}
//如果一列全为0,那么行列式为0
if (!flag)
{
zero = true;
break;
}
}

for (int trow = row + 1; trow < size; trow++){
double k;
//如果当前行已经为0,那么直接跳过,否则用row行乘-k加到trow行
if (data[trow][row] == 0)
continue;
k = data[trow][row] / data[row][row];
for (int col = row; col < size; col++){
data[trow][col] += -k * data[row][col];
}
}
}

double ans;


//整理答案并输出
if (zero)
ans = 0;
else{
ans = sign;
for (int row = 0; row < size; row++){
ans *= data[row][row];
}
}

System.out.print(ans);
}

static void swap_row(int a, int b, int size, double data[][]){
for (int i = a; i < size; i++){
double temp = data[a][i];
data[a][i] = data[b][i];
data[b][i] = temp;
}
}
}

2016年寒假集训

一、时间内容

2017年寒假,在学校ACM集训队开始了为期一个月的集训,感觉收获颇多,于是写这篇文章以来记录。

在这四周里,每周都有训练计划,每天周一至周六,从早上8:00至晚上21:30,差不多整天面对屏幕了,虽然坐在实验室看了一天屏幕,累的腰酸眼疼,但是得到收获让我感觉这些付出都值得。每周末都有一次测试赛,还有为了缓解同学们的压力而开展的集体活动。总的来说这个寒假在集训队里的生活是充实愉快的。

二、体验与经历

接下来写下在集训中的具体体会和经历好了,由于我是中外专业的,结束考试比较早,于是集训早开始三四天,由于考试完整个人太怠惰了,结果集训第一天的时候就迟到了,正好还赶上是分组赛,感觉非常可惜,浪费了一次比赛的机会,并且在一开始就没有跟上集训的步伐。

于是主动和老师道了歉,下午就去实验室和同学一起正常参加集训了,正好在昨天在实验室里同学找到了一个有意思的题,于是下午就在研究这个。刚开始用深度优先搜索推,于是超时,后来改用动态规划,结果推不出来状态转移方程,后来又想了想才发现是一个简单的广度优先搜索。于是就去敲了代码去交一遍试试,一次Accepted,于是就去告诉了同学思路,成就感满满。

然后OJ开了集训的训练专题,之前在《算法竞赛入门经典》里刷过了数据结构和C++的STL,于是刷起来很顺利,一天大概就刷了一半的水题,结果正式集训之前,就差不多完成了所有的题目,然后集训的时候,每天自己只是在补一两道题,于是整个训练都很轻松。在这里我也差不多算是找到了适合自己的学习方法,就是在正式学习,听老师讲课和做题训练,直接先把内容过一遍,因为发现自己更适合直接找一本合适的教材去看书。之前上高数的时候也印证了这一点,每次上课如果没自己看书的话,完全不知道老师所云,之后由于没听懂老师的讲课,于是自己去刷了一遍高数书,结果用了一两个小时过完了老师三四节课的内容,顺便预习了以后的一点内容,然后又上课的时候,发现老师讲的内容就很简单了,最是遇到不太懂的地方补一下就好了。

于是利用这个集训中找到的适合直接的方法去用到下一个学期,在上课和集训之前把内容都自己过一遍。

之后正式开始集训后就进行了分组赛,所幸成绩不错,比赛也算是获得成就感和动力的一个地方了,最后实验室的位置安排和旁边的两个巨巨一组。于是正式的集训就开始了,因为之前刷完了所有的题目,于是只是在刷《算法竞赛入门经典》这一本书,据说是比较难(自己也深有体会TAT),自己也算是从开始到现在坚持下来刷紫书了,收获真的是非常大,书中的一道题,作者就讲了许多的做法,比如一道状态搜索的题,通常做法是暴力广度优先搜索,正好在训练迭代加深搜索,作者也给出了这个的思路,另外还提及了双向广度优先搜索和A*算法。另外就是书中的题好多都是经典题,这里引用一下某位dalao的“刷题哲学”:所谓的经典题就那些,刷完一道少一道。有种很人动力的感觉。由于是经典题网上可以收到很多blog,直接敲完一遍代码后再去翻blog,能学到不少的新知识,之前一道题我暴力模拟写了200行代码,结果搜了下blog,别人开数组映射了一下,代码量直接缩到了100行,顿时感觉非常惊艳,于是赶快对着敲了一遍学习了一下。总的来说这本书真的是一本好书,最近一个月的新知识感觉并不是通过集训,而且自己刷这本书得到的。可能是我比较适合自己去看书刷题吧。

另外不只是算法,由于实验室的学习氛围非常好,自己空闲的时间也学了一些其他的东西,诸如HTML、TCP/IP,Python爬虫之类的,大体上向着互联网的方向上学习的,并且自己开了一台服务器,用Python写了个Web程序,放在服务器上搭了个网页。另外越来越习惯Linux的环境了,现在电脑也是双系统,主要使用Linux,除了用些Linux平台没有的软件,基本上只是在用Linux了。另外由于下学期有Java外教课,直接过了一遍Java的课程,大概把面向对象的知识简单过了一遍。

后来的许多天都是日复一日的训练了,虽说每天的收获都非常多,但是想了想并没有什么值得写的,感觉记忆深刻点的还是比赛吧,最后一场的测试赛现在还是记忆犹新。

原本只是一场普通的测试赛来着,老师突然要求改成组队赛,为了适应不久就要到来的天梯赛(这里以后讲一下),于是就按之前比赛的排名平均权重随机分配了一下。结果运气好匹配到的两个队友也比较给力,出来分配名单后就和他们讨论了一下,讨论下如何分配任务。最后结果是我负责敲代码,另一个队友负责想思路,和帮我一起看代码,另外一个专门负责翻译英文题,没办法讨论的时候发现我们英语都不是很好2333。

比赛第一题是一道英文题,于是直接跳过,让队友去翻译了。然后我和另一个队友去看了第B题,我看了许久没思路,感觉负责敲代码的不能闲着,要一直有思路敲代码才行,于是让队友继续去看这道题,我去看了下C题。看了下是很简单的水题,于是敲了下来,幸好没有立即交上,叫来了队友看了下,结果有一个坑点,判断下左右边界,于是交上去AC了。这时翻译英文题的队友过来了,告诉我了下A题的题意,但是我还是纠结B题于是没打算敲A题。但是这时B题还是没思路,于是我把翻译英文题的队友叫来,一起敲完了A题,两个人检查了一遍,一次AC。无奈这时B题还是没思路,刚开始我的思路是直接暴力嵌套循环的,算了下时间复杂度果断超时,但是可以打一下表,于是打表出来让两个队友去找规律了。这时队友告诉我D题是一个暴力DFS的题,让我去敲,我看了下,刚好之前刷紫书做到过,于是照着敲了下来,过了几组数据一次AC。这时两个队友突然发现B题的递推规律,果断敲了代码,对比了下之前暴力打表的数据,完全符合,交上去,一次AC。这时我也看了下E题,是一个博弈题,看了下没思路,于是让两个队友都去看了,这时我发现F题就是一个无向图的最短路,于是很快就敲了出来,测了下样例交上去一次AC。然后两个给力的队友找到E题的规律了,就是一个if语句就可以解决的题,写了没几行的代码交上去A掉了。这时只剩下H题一个英文题和G题,这时队友已经把所有的题翻译完了。我看了下G题没思路,于是让队友告诉我了下H题的题意,发现是一个麻烦的模拟题,于是让他们去看G题,我去敲H题的代码,大概敲了半个小时,只是一个处理起来麻烦的题,思路不难想,然后敲完过了下样例,没想到交上去居然AC掉了。这时回过头来三个人一起想G题,刚开始我觉得是贪心,后来感觉是动态规划,最后觉得可能是二分递归分治,最后按分治敲了下代码,样例还没过比赛就结束了。

剩下值得记录的就是结训了,最后结训赛,自己已经放弃治疗了,没想到还能水到一个奖品,最后老师讲了下自己寒假的经历,做了下总结,学长(长者)们也上台给我们讲(传授)了下学习方法(人生经验),总的来说过程是非常欢快的,QAQ巨是真的萌,最后要每个专业自荐一个代表讲讲话,然后某学长叫了我的名字2333,于是大概推荐了下紫书,和自己上面所说的学习方法,最后大家一起合影,寒假集训愉快的结束了~

三、一些感悟

之前刘老师也单独找过我谈过话,我当时说过,我当初上小学的时候,只喜欢打游戏,学习只是感兴趣学一下数学,实在是不想继续上初中了。然后是初中,每天浑浑噩噩的学习只是学一下感兴趣的课程,考试得到点成就感,根本没想着继续去上高中。直到后来开设化学和物理,发现原来世界上还有这么有意思的东西,感觉自己如果不了解的话多可惜啊。于是当初中考也只是考了个比较差的民办高中。在高中时追随初中的想法,读了一些物理科普书和名著小说,于是愈加的想要一个更高的平台,去了解更多这样有趣的知识。

然后大学我想继续追随这个想法,开学之前自己其实就搜索了许多ACM相关的资料,于是加入了ACM。

确实ACM这个平台也没辜负我的期望,让我见到了许许多多的dalao,在这个算是不大的圈子里,可以直接和整个中国最优秀的ACMer交流,可以把他们当做自己追赶的目标。另外算法也满足了我对知识的欲望,每次学到一个新算法都是成就感满满,当自己刚学会DFS解决数独问题和迷宫问题时,当自己和队友一起AC掉题时真心感到快乐。

这里借用一下某个ACMer的话:我并不了解人生,也不愿去臆测未来,我只知道一道题WA很久之后突然Accepted的感觉简直妙不可言!

尾巴

其实这时当时写的寒假的社会实践,挺有纪念意义的,以后的也一起放出来好了。

UVA - 11212 Editing a Book(迭代加深搜索 IDA* + 模板)

题目链接


https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2153

题目大意


给定一个长度 1 ~ 9 的整数序列,每个整数 1 ~ 9 。

序列是无序的,你有两种操作,剪切和粘贴,两种操作均可以处理任意长度。

求至少经过多少次操作,可以使序列有序(递增)。

解题过程


本来感觉处理数组比较麻烦,每次都要开辟空间传递指针,然后赋值。

看了一个博客,可以用 string 类处理,然后感觉有现成的类,比手动辅助数组方便太多!!!

本题可以做一个迭代加深搜索的模板。

题目分析


迭代加深搜索(IDA*):

  • 适用范围:可用回溯法,但解答树没有明显上限的问题。
  • 从 0 开始枚举最大迭代深度,然后使用函数迭代求解,如果在当前最大迭代深度下有解,即是答案(处理最少步骤等问题)。
  • 切记,迭代加深搜索,解答树节点一般比较多,尽可能考虑剪枝。
  • 核心代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    for (maxd = 0; ; maxd++)
    {
    if (solve(0))
    break;
    }

    bool solve(int d)
    {
    if (d == maxd)
    return judge();

    if (solve(d+1))
    return true;

    return false;
    }

另外强调下 string 的 substr 方法。
substr(pos, len);
从 pos 开始, len 长度的子串,如果忽略 len ,则一直到结尾。

AC代码


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include<bits/stdc++.h>
using namespace std;

int n, step, maxd;

string goal;

bool solve(int d, string now)
{
if (d == maxd)
{
step = d;
return now == goal;
}

int h = 0;
for (int i = 0; i < n - 1; i++)
if (now[i+1] - now[i] != 1)
h++;

if (h > 3 * (maxd - d))
return false;

for (int l = 0; l < n; l++)
{
for (int r = l; r < n; r++)
{
string slt = now.substr(l, r-l+1);
string tail = now.substr(r+1);
for (int k = 0; k < l; k++)
{
string il = now.substr(0, k);
string ir = now.substr(k, l-k);
if (solve(d+1, il+slt+ir+tail))
return true;
}
}
}
return false;
}

int main()
{
int testCase = 0;
while (~scanf("%d", &n) && n)
{
string data;
for (int i = 0; i < n; i++)
{
int t;
scanf("%d", &t);
data += (char) t + '0';
}

goal = "";
for (int i = '1'; i <= '0'+n; i++)
goal += i;

for (maxd = 0; maxd < n; maxd++)
if (solve(0, data))
{
printf("Case %d: %d\n", ++testCase, maxd);
break;
}
}
}

UVa 1354 - Mobile Computing(二叉树 + DFS)

题目链接


https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=4100

题目大意


给定多个二叉树的叶子,每个节点有一个重量,两个节点可以用一个木棍连接,一个木棍的一头可以连接一个叶子节点,或者另一根木棍。

要保证每个木棍平衡,符合胡克定律。

解题过程


刚开始写的,状态是用一个 vector 储存的,每次删去两个节点,继续归并其他的。
然后来回操作非常麻烦,也一直 WA。

于是百度了下别人的博客,他是每一层 dfs 都开一个 vector 数组储存状态,然后以引用传递,传给下级。

每层一个 vector 储存当前的状态,避免了来回操作一个数组造成 bug。

题目分析


主要是注意下,这里 dfs 时每层储存状态。

AC代码


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include<bits/stdc++.h>
using namespace std;

const int MAX = 112;
const double EPS = 1e-8;

struct node
{
double first;
double second;
double right;
node (double first, double second, double right):first(first),second(second),right(right){}
};

int n;
double width, ans = -1;
vector<node> status;

node connect(node a, node b)
{
double w = a.second + b.second;
double la = b.second / w, lb = a.second / w;
node ans = node(max(a.first + la, -lb + b.first), w,max(b.right + lb, -la + a.right));
return ans;
}

void solve(vector<node> &status)
{
if (status.size() == 1)
{
double t = status[0].first + status[0].right;
if (t <= width + EPS)
ans = max(ans, t);
return;
}

for (int i = 0; i < status.size(); i++)
{
for (int j = 0; j < status.size(); j++)
{
if (i == j)
continue;

vector<node> nstatus;

for (int k = 0; k < status.size(); k++)
{
if (k == i || k == j)
continue;
nstatus.push_back(status[k]);
}

node a = status[i], b = status[j];
node x = connect(a, b);

if (x.first + x.right > width + EPS)
continue;

nstatus.push_back(x);
solve(nstatus);
}
}
}

int main()
{
int T;
scanf("%d", &T);
while (T--)
{
status.clear();
ans = -1;
scanf("%lf", &width);
scanf("%d", &n);
while (n--)
{
double w;
scanf("%lf", &w);
status.push_back(node(0.0, w, 0.0));
}
solve(status);
if (ans < 0)
puts("-1");
else
printf("%.16lf\n", ans);
}
}

UVA 10603 - Fill(dijkstra + 状态图)

题目链接


https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=1544

题目大意


有三个水杯,容量分别为 a, b, c 刚开始 c 水杯注满水,其他是空的,然后求经过 n 次操作后可不可以得到 d 升水。如果可以的话,转移的水量尽量少,如果无法得到 d 升的话,就输出 d’ 升,d’< d 并且 d’ 尽量的大。

这里的转移水量指,总共转移了多少升水,比如 a 向 b 注了五升水,那么转移水量为五升。

解题过程


照着紫书示例敲得,看作者代码学到了好多东西。

题目分析


  • 首先是进行枚举操作的时候,虽然总共三个杯子,作者还是把数据放到了数组里面,简化不少代码,如果是我的话,就直接写9个if了。

  • 然后是代码的思想是 dijkstra,求最短路。

    • 把整个问题当做一个状态图,每个点由三个水杯里面的水确定。
    • 两个点之间的边的权值,是由两个状态转化所需要转移的水量。(注意一点是这里是有向图)
    • 另外三个水杯里面的水合不变,给定两个水杯里水的体积,就可以确定另一个,所以储存状态时,只需要记录两个水杯的体积即可。
  • 经过上面的解析,这个问题就抽象成了,求一个有向图的最短路径,然后使用了优先队列优化的 dijkstra 。

  • 另外memcpy的使用值得一学,使用方法如下:
    memset(&a, &b, sizeof(a));
    把 b 复制给a,直接复制的内存,效率比直接循环快。

AC代码


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include<bits/stdc++.h>
using namespace std;

const int MAX = 200+10;

struct Node
{
int v[3], dist;
};

bool operator < (const struct Node& a, const struct Node& b)
{
return a.dist > b.dist;
}

int vis[MAX][MAX], ans[MAX];

void update_ans(Node u)
{
for (int i = 0; i < 3; i++)
{
int d = u.v[i];
if (ans[d] < 0 || ans[d] > u.dist)
ans[d] = u.dist;
}
}

void solve(int a, int b, int c, int d)
{
int cap[3] = {a,b,c};
memset(vis, 0, sizeof(vis));
memset(ans, -1, sizeof(ans));
Node start;
start.v[0] = 0, start.v[1] = 0, start.v[2] = c;
start.dist = 0;
priority_queue<Node> q;
q.push(start);
vis[0][0] = 1;

while (!q.empty())
{
Node u = q.top(); q.pop();
update_ans(u);

if (ans[d] >= 0)
break;

for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (i == j)
continue;
if (u.v[i] == 0 || u.v[j] == cap[j])
continue;

int amount = min(cap[j], u.v[i] + u.v[j]) - u.v[j];
Node u2;
memcpy(&u2, &u, sizeof(u));
u2.dist = u.dist + amount;
u2.v[i] -= amount;
u2.v[j] += amount;
if (!vis[u2.v[1]][u2.v[0]])
{
vis[u2.v[1]][u2.v[0]] = 1;
q.push(u2);
}
}
}
}
}

int main()
{
int T;
scanf("%d", &T);
while (T--)
{
int a, b, c, d;
scanf("%d %d %d %d", &a, &b, &c, &d);
solve(a, b, c, d);
while (d >= 0)
{
if (ans[d] >= 0)
{
printf("%d %d\n", ans[d], d);
break;
}
d--;
}
}
}

webpy服务器(Linux+Web+HTML)

前言:


刚开始在慕课网上面看到了一个webpy的视频,正好自己有个服务器,想搭个网站玩玩,然后webpy比较简单,所以拿这个动手了,以后可能去用别的库去做网页。

网页也是看了一下午的HTML的标签什么的,CSS还没学,随手写了个简单的,带个 POST 请求。

Web程序:


URL解析:


首先在程序开头放上这样一段代码:

1
2
3
4
urls = ('/','index',
'/(\d+)', 'read',
'/(.*)', 'other')
app = web.application(urls, globals())

这样用来匹配访问url,要去执行那个类。

匹配方式,分精确匹配模糊匹配两种,模糊匹配可以实现带组匹配,即上面加的那一个括号,这样会把括号中匹配的数据以 name 变量传递给类的方法。

实现响应:


每个url匹配后对应一个类,在类里面实现网页中请求的方法就好了,比如 GETPOST

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class index:

def __init__(self):
self.reader = read()

def GET(self):
with open('html/index.html', 'rb') as f:
html = f.read()
return html

def POST(self):
data = web.data()
data = urllib.unquote_plus(data)
with open('temp ', 'wb') as f:
f.write(data)


class read:

def GET(self, name):
print name

启动Web程序:


因为是在 Linux 服务器上运行的,直接输入一下代码:

1
nohup python test.py 45.76.206.109:80 &

记得 ip 后面带上端口号。

HTML网页:


from标签:


这里的 action 大概是指用 method 里面的方法,访问了 [主域名] + [action]
例如下面的,相当于 POSTacmfish.top/,然后根据 URL 匹配,调用了 index 类的 POST 方法。

1
2
3
4
5
6
7
<form method="post" action="/">
<div align="center">
<label>PID:</label>
<input type="text" name="PID">
<input type="submit" value="Submit">
</div>
</form>

全部代码:


Web程序:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import web

urls = ('/','index',
'/(\d+)', 'read',
'/(.*)', 'other')
app = web.application(urls, globals())

class index:

def __init__(self):
self.reader = read()

def GET(self):
with open('html/index.html', 'rb') as f:
html = f.read()
return html

def POST(self):
data = web.data()
return self.reader.GET(data[4:])

class read:

def __init__(self):
self.head = '''<html>
<head>
<meta charset="utf-8"/>
<title>Code</title>
</head>
<body>
<pre>'''
self.tail = '''</pre>
</body>
</html>'''

def GET(self, name):
path = 'code/'+name
try:
with open(path, 'rb') as f:
text = f.read()
return text
except IOError:
return 'File not find'

class other:
def GET(self, name):
return 'Not find'

if __name__ == "__main__":
app.run()

HTML:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>Get Code</title>
<style>
a{
color:#40668c;
}
label{
color:#000000;
}
input{
color:#000000;
}
</style>
</head>
<body>
<h1 align="center">Get Code</h1> <hr color="#000000">
<br><br><br>
<h2 align="center">请直接访问<a href="/1000">45.76.206.109/1000</a>以获取PID为1000题目代码。</h2>
<h2 align="center"></h2>
<h2 align="center">快捷搜索</h2>
<form method="post" action="/">
<div align="center">
<label>PID:</label>
<input type="text" name="PID">
<input type="submit" value="Submit">
</div>
</form>


</body></html>

SDUT 2622 - 最短路径(SPFA+二维)

题目链接


http://acm.sdut.edu.cn/onlinejudge2/index.php/Home/Index/problemdetail/pid/2622.html

题目大意


有一个有向图,给定一个终点和起点,求起点到终点的最短路径,并且路径经过的边数是 x 的倍数。

解题过程


想了好长时间,最初是想把每次的步数一起装到队列里面,用 SPFA 。
然后 WA , 只好去搜了下博客,原来是多了个维度,和之前做的一个题神似,UVA1600 ,这个是多了一个维度的BFS,以后得想起来这个加一个维度解决问题的方法了,要不遇到就卡死。

题目分析


  • 大体思路是用 SPFA ,增加一个维度,储存步数对 x 取模,最后输出对 x 取模后是 0 的终点距离即可。

AC代码


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include<bits/stdc++.h>
using namespace std;

struct node
{
int v;
long long w;
node(int v, long long w):v(v),w(w){}
};

vector<node> edges[112];
bool book[112];
long long dis[112][11];
int s, e, x;
int n, m;

int main()
{
int T;
scanf("%d", &T);
while (T--)
{
scanf("%d %d", &n, &m);
memset(book, 0, sizeof(book));
memset(dis, -1, sizeof(dis));
for (int i = 0; i < n; i++)
edges[i].clear();

for (int i = 0; i < m; i++)
{
int u, v;
long long w;
scanf("%d %d %lld", &u, &v, &w);
edges[u].push_back(node(v,w));
}

scanf("%d %d %d", &s, &e, &x);
queue<int> q;
q.push(s);
dis[s][0] = 0;

while (!q.empty())
{
int u = q.front();

for (int i = 0; i < edges[u].size(); i++)
{
int v = edges[u][i].v;
long long w = edges[u][i].w;
for (int k = 0; k < x; k++)
{
if (dis[u][k] != -1 && (dis[v][(k+1)%x] > dis[u][k] + w || dis[v][(k+1)%x] == -1))
{
dis[v][(k+1)%x] = dis[u][k] + w;
if (!book[v])
{
q.push(v);
book[v] = 1;
}
}
}
}
book[u] = 0;
q.pop();
}

if (dis[e][0] == -1)
printf("No Answer!\n");
else
printf("%lld\n", dis[e][0]);
}
}