这周基本都是搜索题,做的快吐血了,虽然不是非常难,但是精细的问题还是挺多的,还有一些之前很少遇到的tle和mle,听说还有很多人格式错误,算是“五毒俱全”了,所以每道题还是有不少探讨的地方的。
A
之前想要搞一些花头,结果每次都wa了,所以最后直接暴力写了一个,倒是通过了,不过有点慢就是了。
# include <cstdio> int main() { char s[1010]; while(~scanf("%s", s)) { int i, k; bool f = 1; while(f) { f = 0; k = 0; for(i = 0 ; s[i] ; i++) { if(s[i] == 'y' && s[i + 1] == 'y' && !f) { s[k++] = 'Y'; i++; f = 1; } else if(s[i] == 'Y' && s[i + 1] == 'Y' && !f) { i++; f = 1; } else s[k++] = s[i]; } s[k] = 0; } puts(s); } return 0; }
B
这道题和约瑟夫环那道题挺像的,可以用类似xdoj1018里我的方法做,因为这个每次消减的更多,高达一半或者三分之二,所以复杂度仅有logn。说说具体的做法,把每次的剩余元素都用1至k编号,会发现每次的编号和消除前的编号存在某种给定的映射关系,从而解决。
# include <cstdio> int main() { int T; scanf("%d", &T); while(T--) { int N; scanf("%d", &N); int i = 0, j, k; while(N > 3) { N -= N / (2 + (i & 1)); i++; } for(j = 1 ; j <= N ; j++) { int z = j; for(k = i - 1 ; k >= 0 ; k--) { if(k & 1) z = (z - 1) / 2 + z; else z = 2 * z - 1; } printf("%d%s", z, (j == N) ? "\n" : " "); } } return 0; }
C
这道题之前听说很多人tle了就做了一下,如果硬bfs的话的确会tle,不过可以注意一个细节,减法只能减一,所以如果当前已经超过m了我们再去加倍肯定是亏的,我们可以直接省略之后的步骤,判断一路减法是否更小,如果不行就跳过好了,另外如果当前步数已经超过最小值也可以直接continue,这样时间就降下来了,不过这道题更好的做法好像是dp。另外因为忘了可能到不了wa了一发。其他还好。
# include <cstdio> # include <cstring> # include <queue> # include <algorithm> using namespace std; const int MAX_N = 1e6; const int INF = 1e9; int d[2 * MAX_N + 2]; int N, M; int main() { while(~scanf("%d %d", &N, &M)) { if(N == 0 && M != 0) { puts("-1"); continue; } memset(d , -1 , sizeof(int) * 2 * M); int ans = INF; queue<int> que; que.push(N); d[N] = 0; while(!que.empty()) { int y = que.front(); que.pop(); if(d[y] > ans) continue; if(y >= M) { ans = min(ans , d[y] + y - M); continue; } if(d[2 * y] == -1) { d[2 * y] = d[y] + 1; que.push(2 * y); } if(y - 1 >= 0 && d[y - 1] == -1) { d[y - 1] = d[y] + 1; que.push(y - 1); } } printf("%d\n", ans); } return 0; }
D
我这个方法的好处是具备可移植性,所以在C代码操作里加了三行之后就可以了。
# include <cstdio> # include <cstring> # include <queue> # include <algorithm> using namespace std; const int MAX_N = 1e6; const int INF = 1e9; int d[2 * MAX_N + 2]; int N, M; int main() { while(~scanf("%d %d", &N, &M)) { memset(d , -1 , sizeof(int) * 2 * M); int ans = INF; queue<int> que; que.push(N); d[N] = 0; while(!que.empty()) { int y = que.front(); que.pop(); if(d[y] > ans) continue; if(y >= M) { ans = min(ans , d[y] + y - M); continue; } if(d[2 * y] == -1) { d[2 * y] = d[y] + 1; que.push(2 * y); } if(y - 1 >= 0 && d[y - 1] == -1) { d[y - 1] = d[y] + 1; que.push(y - 1); } if(d[y + 1] == -1) { d[y + 1] = d[y] + 1; que.push(y + 1); } } printf("%d\n", ans); } return 0; }
E
这题我承认我做的很糟糕,用了一个非常暴力的做法(换句话说,可优化空间巨大),然后经过一些优化勉强过关(997ms),所以如果你提交这个tle,也是正常的,因为这个代码在tle与不tle之间游离(:。做法很暴力,函数dfs外加vector传递,重点是优化,首先因为这些数加起来大于2,所以必须是一奇一偶交替,那么所有的奇数就肯定无解了,而且在搜索的时候也直接奇数偶数搜索即可了。
# include <cstdio> # include <cstring> # include <vector> using namespace std; bool used[19]; bool p[40]; int N; int ans; void sea(vector<int> pi , int n , int prv) { pi.push_back(prv); int i; if(n == N) { if(!p[1 + prv]) return; //ans++; int s = pi.size(); for(i = 0 ; i < s ; i++) printf("%d%s", pi[i], (i == s - 1) ? "\n" : " "); return; } for(i = -(prv & 1) + 3 ; i <= N ; i += 2) { if(used[i] || !p[prv + i]) continue; used[i] = 1; sea(pi , n + 1 , i); used[i] = 0; } } int main() { int tt = 0; p[2] = p[3] = p[5] = p[7] = p[11] = p[13] = p[17] = p[19] = p[23] = p[29] = p[31] = p[37] = 1; while(~scanf("%d", &N)) { memset(used , 0 , sizeof(used)); ans = 0; printf("Case %d:\n", ++tt); if((N & 1)) ; else sea({} , 1 , 1); //printf("%d\n", ans); puts(""); } return 0; }
F
这道题也算是让我学到了一些东西,一开始的想法是用尺取法找出每种幂次的连续区间个数,这样复杂度就是nlogn,但之后在基本完成之后遇到一个比较棘手的问题,就是这个数列里面有负数,这样sum数列单增的性质就不存在了,所以我上网看了看资料,发现其实尺取法是可以处理负数的情况的,只要实现排个序即可,因为这样任何sum数列的差值依然是一个区间和,只不过可能是区间和的相反数,所以只要用一个pair来维护sum,加入这个sum的末尾编号即可,这样只要大减小时编号大的更大就是成立的。这里为了防止可能发生的错误我用了稳定排序,不过之后发现这个是不必要的,sort也没问题(因为pair的性质),没有明白的不妨思考看看。
2018、5、23
没想到这道题给我造成的难度比之前以为的还要大,因为没有发现会存在k为负数的情况,不过因为负数的时候只要考察绝对值相同并且是大的数序号小即可,之后再实战cf原题的时候又遇到了一个退化的问题,所以在查找lb的时候用到了一个二分优化从而解决了时间的问题。代码已经更新。
# include <cstdio> # include <algorithm> using namespace std; typedef long long ll; typedef pair<ll , int> P; const int MAX_N = 1e5; P S[MAX_N + 1]; int N, K; int main() { while(~scanf("%d %d", &N, &K)) { int i; S[0].first = S[0].second = 0; for(i = 1 ; i <= N ; i++) { int t; scanf("%d", &t); S[i].first = S[i - 1].first + t; S[i].second = i; } stable_sort(S , S + N + 1); ll p = 1; ll ans = 0; while(abs(p) <= S[N].first - S[0].first) { int lb = 0, ub = 0; while(ub <= N) { while(ub <= N && S[ub].first - S[lb].first < abs(p)) ub++; if(ub > N) break; if(S[ub].first - S[lb].first == abs(p)) { if(p > 0) ans += upper_bound(S , S + N + 1 , P(S[ub].first , 1000000)) - lower_bound(S , S + N + 1 , P(S[ub].first , S[lb].second)); else ans += upper_bound(S , S + N + 1 , P(S[ub].first , S[lb].second)) - lower_bound(S , S + N + 1 , P(S[ub].first , -1)); } lb++; } p *= K; if(p == 1) break; } printf("%d\n", ans); } return 0; }
G
这道题让我想了一阵,一开始觉得这个路线非常复杂,不过在仔细想了一阵之后发现其实本质依然是一个搜索问题,因为我们完全可以为其建立和一个普通图完全一样的单向边,而且可以发现这是一个DAG,不过我没有太从这个方向思考。我还是用的最常规的bfs,唯一的区别在于如何标记,因为这个找的是最长路线,所以后面的路线会覆盖前面的路线,所以如果想要处理这个点必须是不存在其他更长路径的情况下才能处理,这样这道题就和常规的bfs无异了。
# include <stdio.h> # include <algorithm> # include <queue> # include <cstring> using namespace std; typedef pair<int , int> P; typedef pair<P , int> W; const int MAX_N = 1000; int dx[4] = {1 , 0 , -1 , 0}, dy[4] = {0 , 1 , 0 , -1}; bool used[MAX_N][MAX_N]; int h[MAX_N][MAX_N]; int N, M, T; inline bool ok(P pos , P prv) { int i; for(i = 0 ; i < 4 ; i++) { P y = P(dx[i] + pos.first , dy[i] + pos.second); if(y.first >= 0 && y.first < N && y.second >= 0 && !used[y.first][y.second] && y.second < M && y != prv && h[pos.first][pos.second] > h[y.first][y.second]) return 0; } return 1; } int main() { scanf("%d", &T); while(T--) { memset(used , 0 , sizeof(used)); scanf("%d %d", &N, &M); int i, j; for(i = 0 ; i < N ; i++) for(j = 0 ; j < M ; j++) scanf("%d", &h[i][j]); queue<W> que; for(i = 0 ; i < N ; i++) { for(j = 0 ; j < M ; j++) { if(ok(P(i , j) , P(-1 , -1))) { used[i][j] = 1; que.push(W(P(i , j) , 1)); } } } int ans = 0; while(!que.empty()) { P no = (que.front()).first; int l = (que.front()).second; // que.pop(); bool f = 1; for(i = 0 ; i < 4 ; i++) { int nx = dx[i] + no.first, ny = dy[i] + no.second; if(nx >= 0 && nx < N && ny >= 0 && ny < M && h[nx][ny] > h[no.first][no.second]) { f = 0; if(ok(P(nx , ny) , no)) { used[nx][ny] = 1; que.push(W(P(nx , ny) , l + 1)); } } } if(f) ans = max(ans , l); } printf("%d\n", ans); } return 0; }
H
这道题和xdoj1164这道很相似,我得感谢这道题的范围高达1e7,才让我对这类问题有了更加深入的思考和理解,也为我做这道题省了不少时间,这类问题如果是正常去看的话都认为应该从上面向下递归,其实不然,因为这种子树全部操作的问题往往任意节点之和他的父亲节点有关,这点我在那篇博客(碰巧那道题我写了一篇博客)有了详细的介绍。还有就是建边问题,对于这种树有一种比较方便的建边方法,就是用一个数组存储它们父亲节点的编号,因为父亲节点是唯一的,但这样的问题是只能从后向前研究,不过对于这道题这种方法不但没有坏处,反而使得这两个技巧相得益彰,使得代码变得出奇得简洁,也算是很有趣的一件事。从1164这道题我也学到了一个道理,有时候只有真的逼迫了自己,才能有新的提升,如果当初不是那道题卡mle卡的太死了,我估计这道题不会想到这个方法。
# include <cstdio> # include <cstring> const int MAX_N = 1e4; int par[MAX_N]; int C[MAX_N]; int T, N; int main() { scanf("%d", &T); while(T--) { scanf("%d", &N); int i; for(i = 1 ; i < N ; i++) scanf("%d", &par[i]); for(i = 0 ; i < N ; i++) scanf("%d", &C[i]); int ans = 1; for(i = 1 ; i < N ; i++) { if(C[i] != C[par[i] - 1]) ans++; } printf("%d\n", ans); } return 0; }
I
这题tle了几次,最后优化了一阵之后成功了,估计方法也不是很好。思路是每次转弯就搜索一次,直到搜了三次,然后看看是否覆盖终点。如果只是常规搜索的话可能会出现n立方的情况,所以用一个bool储存是否已经搜索完毕x方向和y方向(这两个正负方向必然同时完毕),这样就少了一维。
# include <cstdio> # include <algorithm> # include <queue> using namespace std; typedef pair<int , int> P; typedef pair<P , int> W; const int MAX_N = 1e3; int dx[4] = {1 , 0 , -1 , 0}, dy[4] = {0 , 1 , 0 , -1}; bool kan[2][MAX_N][MAX_N]; int used[MAX_N][MAX_N]; char maz[MAX_N][MAX_N + 1]; int N, M; void solve() { int sx, sy, tx, ty; int i, j; for(i = 0 ; i < N ; i++) { fill(used[i] , used[i] + M , -1); for(j = 0 ; j < 2 ; j++) fill(kan[j][i] , kan[j][i] + M , 0); } for(i = 0 ; i < N ; i++) { scanf("%s", maz[i]); for(j = 0 ; j < M ; j++) { if(maz[i][j] == 'S') { sx = i; sy = j; } else if(maz[i][j] == 'T') { tx = i; ty = j; } } } queue<W> que; que.push(W(P(sx , sy) , -1)); while(!que.empty()) { int px = (que.front()).first.first, py = (que.front()).first.second; que.pop(); for(i = 0 ; i < 4 ; i++) { if(kan[i & 1][px][py]) continue; int nx = px + dx[i], ny = py + dy[i]; while(nx >= 0 && nx < N && ny >= 0 && ny < M && maz[nx][ny] != '*') { kan[i & 1][nx][ny] = 1; if(used[nx][ny] == -1) { used[nx][ny] = used[px][py] + 1; if(used[nx][ny] < 2) que.push(W(P(nx , ny) , i)); } nx += dx[i]; ny += dy[i]; } } kan[0][px][py] = kan[1][px][py] = 1; } if(used[tx][ty] == -1) puts("NO"); else puts("YES"); } int main() { while(~scanf("%d %d", &N, &M)) { solve(); } return 0; }
J
这道题的重点在于思维,当然操作是用到了搜索,第一眼看上去以为是类似马车夫问题那样的状压dp,不过看到n的范围之后发现并不是这样,之后发现这是一个树,所以就开始在树上面下功夫,从而得出一个结论,容我慢慢道来。首先可以注意到每两个节点之间路径是唯一的,所以发现从任何节点出发最后回来,能够遇到的点恰恰是途经边数的两倍,所以说如果这道题问的是从这里出发并且最后回来,那么答案最大(不能超过b)就是钱数的一半加一(还得算自己)了,那么不需要回来时怎么计算呢,考虑最后停留的节点,我们“赖掉”的最多金钱恰恰就是这个节点到起点的路径的钱,所以我们找到最长的那条路径就可以知道结果了。
# include <cstdio> # include <algorithm> # include <vector> using namespace std; const int MAX_N = 1e5; vector<int> G[MAX_N]; int T, N, M; int sea(int n , int prv) { int i; int ans = 0, s = G[n].size(); for(i = 0 ; i < s ; i++) { if(G[n][i] == prv) continue; ans = max(ans , 1 + sea(G[n][i] , n)); } return ans; } int main() { scanf("%d", &T); while(T--) { scanf("%d", &N); int i; int u, v; for(i = 0 ; i < N - 1 ; i++) { scanf("%d %d", &u, &v); u--; v--; G[u].push_back(v); G[v].push_back(u); } scanf("%d", &M); for(i = 0 ; i < M ; i++) { scanf("%d %d", &u, &v); u--; int lai = min(sea(u , -1) , v); printf("%d\n", min(N , 1 + (lai + v) / 2)); } for(i = 0 ; i < N ; i++) G[i].clear(); } return 0; }
K
一道标准的bfs题,两个人为起点分别做一次即可,注意可能出现都去不了的情况(我没有用INF填充,所以出了一次错)。
# include <cstdio> # include <algorithm> # include <queue> using namespace std; const int MAX_N = 200; typedef pair<int , int> P; int dx[4] = {1 , 0 , -1 , 0}, dy[4] = {0 , 1 , 0 , -1}; P pos[2]; P rest[MAX_N * MAX_N]; int num; int d[2][MAX_N][MAX_N]; char maz[MAX_N][MAX_N + 1]; int N, M; int main() { while(~scanf("%d %d", &N, &M)) { int i, j; num = 0; for(i = 0 ; i < N ; i++) { for(j = 0 ; j < 2 ; j++) fill(d[j][i] , d[j][i] + M , -1); } for(i = 0 ; i < N ; i++) { scanf("%s", maz[i]); for(j = 0 ; j < M ; j++) { if(maz[i][j] == '@') rest[num++] = P(i , j); else if(maz[i][j] == 'M') pos[0] = P(i , j); else if(maz[i][j] == 'Y') pos[1] = P(i , j); } } for(i = 0 ; i < 2 ; i++) { int (*now)[MAX_N] = d[i]; int x = pos[i].first, y = pos[i].second; queue<P> que; que.push(P(x , y)); now[x][y] = 0; while(!que.empty()) { int lx = (que.front()).first, ly = (que.front()).second; que.pop(); for(j = 0 ; j < 4 ; j++) { int nx = lx + dx[j], ny = ly + dy[j]; if(nx < N && nx >= 0 && ny < M && ny >= 0 && maz[nx][ny] != '#' && now[nx][ny] == -1) { now[nx][ny] = now[lx][ly] + 1; que.push(P(nx , ny)); } } } } int ans = 1e9; for(i = 0 ; i < num ; i++) { if(d[0][rest[i].first][rest[i].second] == -1) continue; ans = min(ans , d[0][rest[i].first][rest[i].second] + d[1][rest[i].first][rest[i].second]); } printf("%d\n", ans * 11); } return 0; }
L
一道稍有挑战的bfs题,只要保证可以走到门那一格(这个条件容易被忽略,请注意)并且找齐钥匙就从那扇门开始搜索,搜索完毕后看看公主那里是否ok即可。
# include <cstdio> # include <queue> # include <cstring> # include <algorithm> using namespace std; const int MAX_N = 20; const int MAX_M = 5; typedef pair<int , int> P; int dx[4] = {1 , 0 , -1 , 0}, dy[4] = {0 , 1 , 0 , -1}; char maz[MAX_N][MAX_N + 1]; bool used[MAX_N][MAX_N]; P pos[MAX_M]; int box[MAX_M]; int gai[MAX_M]; int N, M; int main() { while(1) { scanf("%d %d", &N, &M); if(N == 0 && M == 0) break; memset(used , 0 , sizeof(used)); memset(gai , 0 , sizeof(gai)); memset(box , 0 , sizeof(box)); int sx, sy, gx, gy; int i, j; for(i = 0 ; i < N ; i++) { scanf("%s", maz[i]); for(j = 0 ; j < M ; j++) { if(maz[i][j] >= 'A' && maz[i][j] <= 'E') { pos[maz[i][j] - 'A'].first = i; pos[maz[i][j] - 'A'].second = j; } else if(maz[i][j] >= 'a' && maz[i][j] <= 'e') { gai[maz[i][j] - 'a']++; } else if(maz[i][j] == 'S') { sx = i; sy = j; } else if(maz[i][j] == 'G') { gx = i; gy = j; } } } queue<P> que; que.push(P(sx , sy)); used[sx][sy] = 1; while(!que.empty()) { int x = (que.front()).first, y = (que.front()).second; que.pop(); for(i = 0 ; i < 4 ; i++) { int nx = x + dx[i], ny = y + dy[i]; if(nx >= 0 && nx < N && ny >= 0 && ny < M && maz[nx][ny] != 'X' && !used[nx][ny]) { used[nx][ny] = 1; char c = maz[nx][ny]; if(c >= 'a' && c <= 'e') { que.push(P(nx , ny)); box[c - 'a']++; if(box[c - 'a'] == gai[c - 'a'] && used[pos[c - 'a'].first][pos[c - 'a'].second]) que.push(pos[c - 'a']); } else if(c >= 'A' && c <= 'E') { if(box[c - 'A'] == gai[c - 'A']) que.push(pos[c - 'A']); } else que.push(P(nx , ny)); } } } if(used[gx][gy]) puts("YES"); else puts("NO"); } return 0; }