《挑战程序设计竞赛》课后练习题解集——3.2 常用技巧精选
Posted hs-zlq
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《挑战程序设计竞赛》课后练习题解集——3.2 常用技巧精选相关的知识,希望对你有一定的参考价值。
常用技巧精选(一)
尺取法
POJ 2566 给出一个长度n(<=1e5)的数列,每个数的大小在-1e4-1e4之间,给出若干询问值,要求一段字串和,它的绝对值与询问值最接近
好题目。由于数列有正有负,所以不能直接二分或尺取。考虑对前缀和排序 得到一个新数列,此时新数列任意一段子串都对应原数列的一个子串,当左右端点的下标颠倒时,字串和也会添一个负号,但是最后要取绝对值所以可以忽略。最后对新数列尺取即可
1 #include <algorithm> 2 #include <cstdio> 3 #include <iostream> 4 using namespace std; 5 #define pii pair<int, int> 6 7 const int inf = 0x7f7f7f7f; 8 9 int n, k, t; 10 int a[100005]; 11 pii p[100005]; 12 13 int main() { 14 while (scanf("%d%d", &n, &k) != EOF && n != 0 && k != 0) { 15 p[0] = pii(0, 0); 16 for (int i = 1; i <= n; i++) { 17 scanf("%d", &a[i]); 18 p[i] = pii(p[i - 1].first + a[i], i); 19 } 20 sort(p, p + n + 1); 21 for (int i = 0; i < k; i++) { 22 scanf("%d", &t); 23 int l = 0, r = 1, mindis = inf, z, y, dis; 24 while (r <= n && mindis) { 25 int d = p[r].first - p[l].first; 26 if (abs(d - t) < mindis) { 27 mindis = abs(d - t); 28 dis = d; 29 z = p[l].second; 30 y = p[r].second; 31 } 32 if (d < t) r++; 33 if (d > t) l++; 34 if (l == r) r++; 35 } 36 if (z > y) swap(z, y); 37 printf("%d %d %d ", dis, z + 1, y); 38 } 39 } 40 }
POJ 2739 给出一个数(<=1e4),问可以由多少种连续的质数加和得到
裸的尺取
1 #include <algorithm> 2 #include <cmath> 3 #include <cstdio> 4 #include <iostream> 5 using namespace std; 6 #define ll long long 7 8 const int maxnum = 1e4; 9 int prim[maxnum], pvis[maxnum + 5], pcnt; 10 11 void getprim() { 12 for (int i = 2; i <= maxnum; i++) { 13 if (!pvis[i]) prim[++pcnt] = i; 14 for (int j = 1; j <= pcnt && prim[j] * i <= maxnum; j++) { 15 pvis[prim[j] * i] = 1; 16 if (i % prim[j] == 0) break; 17 } 18 } 19 } 20 21 int n; 22 23 int main() { 24 getprim(); 25 while (scanf("%d", &n) != EOF && n) { 26 int res = 0, tmp = 0; 27 for (int l = 0, r = 0;;) { 28 while (r < pcnt && tmp < n) tmp += prim[++r]; 29 if (tmp == n) res++; 30 if (tmp < n || l >= pcnt) break; 31 tmp -= prim[++l]; 32 } 33 printf("%d ", res); 34 } 35 }
POJ 2100 给出一个数(<=1e14),问可以由多少种连续的平方数加和得到,并输出具体方案
仍然是裸的尺取
只是poj计算评测时间,Case time究竟是怎么回事,我凌乱了……这题是单组数据,maxn取1e7 TLE了,然后取sqrt(n)+1就A了;又交了发检测数据有没有大于5e13,事实上是有的……,WTF
1 #include <algorithm> 2 #include <cmath> 3 #include <cstdio> 4 #include <iostream> 5 #include <vector> 6 using namespace std; 7 #define ll long long 8 #define pii pair<int, int> 9 #define fi first 10 #define se second 11 #define pb push_back 12 13 int maxn; 14 15 ll n; 16 vector<pii> v; 17 bool cmp(const pii &p1, const pii &p2) { return p1.se - p1.fi > p2.se - p2.fi; } 18 19 int main() { 20 scanf("%lld", &n); 21 ll tmp = 0; 22 maxn = (int)sqrt((double)n) + 1; 23 for (ll l = 1, r = 1;;) { 24 while (r <= maxn && tmp < n) { 25 tmp += r * r; 26 r++; 27 } 28 if (tmp < n) break; 29 if (tmp == n) v.pb(pii(l, r - 1)); 30 tmp -= l * l; 31 l++; 32 } 33 sort(v.begin(), v.end(), cmp); 34 printf("%d ", v.size()); 35 for (int i = 0; i < v.size(); i++) { 36 printf("%d ", v[i].se - v[i].fi + 1); 37 for (int j = v[i].fi; j <= v[i].se; j++) 38 printf("%d%c", j, j == v[i].se ? ‘ ‘ : ‘ ‘); 39 } 40 }
反转
关于书上例题 POJ 3279为什么一定是从最左边黑色的格子开始反转,而不是让反转区间稍左一点,然后再一系列操作把左边反转成黑色的反回来。
考虑数学归纳。当第一个格子是黑色时,显然成立;当第i个格子是黑色,左边都是白色时,如果把左边某一个反成黑色,根据假设,又要把其中最左边的反回来,这样对同一区间反转两次,必然不是最优解。至此,假设成立。
POJ 3185 有20格喷泉,有0和1两种状态,每次可以反转其中一个及其左右(如果有),问最少可以反转几次使其全0
与上述例题略有不同, 如果第一格是1,有两种方法让它反转,即 反转12或反转123,而我们不知道哪一种情况是最优的。并且,即使第一个是0,也不能说就不进行反转12这种操作。所以我们分类讨论是否进行反转12这种操作,之后剩下的操作就又变回原来例题那种情况了。
1 #include <cstdio> 2 #include <iostream> 3 using namespace std; 4 5 int a[25], b[25], s1, s2; 6 7 int main() { 8 for (int i = 1; i <= 20; i++) { 9 scanf("%d", &a[i]); 10 b[i] = a[i]; 11 } 12 for (int i = 1; i <= 19; i++) { 13 if (a[i]) { 14 a[i + 1] ^= 1; 15 a[i + 2] ^= 1; 16 s1++; 17 } 18 } 19 if (a[20]) s1 = 100; 20 b[1] ^= 1; 21 b[2] ^= 1; 22 s2 = 1; 23 for (int i = 1; i <= 19; i++) { 24 if (b[i]) { 25 b[i + 1] ^= 1; 26 b[i + 2] ^= 1; 27 s2++; 28 } 29 } 30 if (b[20]) s2 = 100; 31 printf("%d", min(s1, s2)); 32 }
POJ 1222 与“棋盘翻转”完全一致
1 #include <algorithm> 2 #include <cassert> 3 #include <cmath> 4 #include <cstdio> 5 #include <cstring> 6 #include <iostream> 7 using namespace std; 8 9 int dx[5] = {0, 1, 0, -1, 0}, dy[5] = {0, 0, 1, 0, -1}; 10 int title[5][6]; 11 int opt[5][6], flip[5][6]; 12 int t, res; 13 14 int get(int x, int y) { 15 int c = title[x][y]; 16 for (int i = 0; i < 5; i++) { 17 int nx = x + dx[i], ny = y + dy[i]; 18 if (nx >= 0 && nx < 5 && ny >= 0 && ny < 6) { 19 c += flip[nx][ny]; 20 } 21 } 22 return c & 1; 23 } 24 25 int main() { 26 scanf("%d", &t); 27 for (int c = 1; c <= t; c++) { 28 for (int i = 0; i < 5; i++) 29 for (int j = 0; j < 6; j++) scanf("%d", &title[i][j]); 30 int res = -1; 31 for (int i = 0; i < 1 << 6; i++) { 32 memset(flip, 0, sizeof(flip)); 33 for (int j = 0; j < 6; j++) { 34 if (i >> j & 1) flip[0][j] = 1; 35 } 36 for (int j = 1; j < 5; j++) { 37 for (int k = 0; k < 6; k++) 38 if (get(j - 1, k)) flip[j][k] = 1; 39 } 40 int fail = 0; 41 for (int j = 0; j < 6; j++) 42 if (get(4, j)) fail = 1; 43 if (!fail) { 44 int tmp = 0; 45 for (int j = 0; j < 5; j++) { 46 for (int k = 0; k < 6; k++) tmp += flip[j][k]; 47 } 48 if (res == -1 || tmp < res) { 49 res = tmp; 50 memcpy(opt, flip, sizeof(flip)); 51 } 52 } 53 } 54 assert(res != -1); 55 printf("PUZZLE #%d ", c); 56 for (int i = 0; i < 5; i++) { 57 for (int j = 0; j < 6; j++) 58 printf("%d%c", opt[i][j], j == 5 ? ‘ ‘ : ‘ ‘); 59 } 60 } 61 }
弹性碰撞
思维点主要在于,不关注具体碰撞过程,只需要结果坐标,再各小球顺序不会改变,就可以知道最后的状态
POJ 2674 一维坐标上有一些人,以“小球碰撞”模型运动,问谁最后离开[0,N]区间
好题目。先按原来的套路得出最后离开区间的答案及其对应的人。把“最晚离开”这一特性比作一个火炬,如果拿火炬的人一路上都没有与人相遇,那么他就是最后一个离开;否则,第一个与他相遇的人将继承这个火炬,保持原来的方向继续运动。因此,我们按最初得到的人的朝向,遍历一遍看会传递到谁即可。
1 #include <bits/stdc++.h> 2 using namespace std; 3 #define ll long long 4 #define inc(i, l, r) for (int i = l; i <= r; i++) 5 #define dec(i, l, r) for (int i = l; i >= r; i--) 6 7 const int maxn = 1e6 + 5; 8 9 char s[maxn]; 10 int n, col[maxn], res; 11 12 int main() { 13 cin >> n; 14 scanf("%s", s + 1); 15 for (char c = ‘a‘; c <= ‘z‘; c++) { 16 int i = n; 17 while (i >= 1 && s[i] != c) i--; 18 for (; i >= 1;) { 19 int j = i - 1; 20 while (j >= 1 && s[j] != c) { 21 if (s[j] > c && col[j] == col[i]) col[j] = col[i] + 1; 22 j--; 23 } 24 i = j; 25 } 26 } 27 inc(i, 1, n) res = max(res, col[i]); 28 if (res > 1) 29 printf("NO "); 30 else { 31 printf("YES "); 32 inc(i, 1, n) printf("%d", col[i]); 33 } 34 }
折半枚举
POJ 3977 给出N(<=35)个数(-1e15~1e15),求一个非空集的和的绝对值是最小,值相同时要使集合的大小最小
一道没想好很容易WA的题。本来是可以直接按折半枚举的套路做,外加判断该方案是否为空。但是,如果对预处理出的集合采用了去重操作,可能会出事。(不知道不预处理,会不会有数据,导致每次都O(n)去找比-x略小的值。再加个random_shuffle貌似可以避免 0 0 0 1 1 1这种数据。不过,暂时还是先按去重的写法来。)本来总和相同的情况下可以只考虑使用数字最少的方案。但是这题要求方案必须是非空集,有可能使用了总和为0,数量不为0的方案;所以如果有去重的操作就会把要使用的方案给去掉了。个人采用的对策是分类讨论枚举部分是否为空
1 #include <algorithm> 2 #include <cstdio> 3 #include <iostream> 4 using namespace std; 5 #define ll long long 6 #define pii pair<ll, int> 7 #define fi first 8 #define se second 9 10 ll a[40]; 11 pii p[(1 << 20) + 5]; 12 13 ll abs_(ll x) { return x > 0 ? x : -x; } 14 15 int main() { 16 int n; 17 while (scanf("%d", &n) != EOF && n) { 18 for (int i = 0; i < n; i++) scanf("%lld", &a[i]); 19 int n2 = n / 2; 20 21 pii res = pii(abs_(a[0]), 1); 22 23 for (int i = 0; i < 1 << n2; i++) { 24 p[i] = pii(0, 0); 25 for (int j = 0; j < n2; j++) { 26 if (i >> j & 1) { 27 p[i].fi += a[j]; 28 p[i].se++; 29 } 30 } 31 if (i > 0) res = min(res, pii(abs_(p[i].fi), p[i].se)); 32 } 33 sort(p, p + (1 << n2)); 34 int m = 1; 35 for (int i = 1; i < 1 << n2; i++) { 36 if (p[m - 1].fi < p[i].fi) p[m++] = p[i]; 37 } 38 39 for (int i = 1; i < 1 << (n - n2); i++) { 40 pii tmp = pii(0, 0); 41 for (int j = 0; j < n - n2; j++) { 42 if (i >> j & 1) { 43 tmp.fi += a[j + n2]; 44 tmp.se++; 45 } 46 } 47 int pos = lower_bound(p, p + m, pii(-tmp.fi, 0)) - p; 48 for (int j = -1; j <= 0; j++) { 49 if (j + pos >= 0 && j + pos < m) 50 res = min(res, pii(abs_(tmp.fi + p[pos + j].fi), 51 tmp.se + p[pos + j].se)); 52 } 53 } 54 printf("%lld %d ", res.fi, res.se); 55 } 56 }
POJ 2549 给出N(<=1000)个数(-5e9~5e9),问能否取出互异的a,b,c,d使得a+b+c=d
1 #include <algorithm> 2 #include <cmath> 3 #include <cstdio> 4 #include <iostream> 5 using namespace std; 6 #define ll long long 7 8 const int maxn = 1e6 + 5; 9 10 ll num[maxn]; 11 struct node { 12 ll add; 13 int a, b; 14 bool operator<(const node& o) const { return add < o.add; } 15 } ab[maxn]; 16 int n, n2, top; 17 ll res; 18 19 int main() { 20 while (scanf("%d", &n) != EOF && n) { 21 top = 0, res = (ll)-1e15; 22 for (int i = 0; i < n; i++) scanf("%lld", &num[i]); 23 for (int i = 0; i < n; i++) { 24 for (int j = i + 1; j < n; j++) ab[top++] = {num[i] + num[j], i, j}; 25 } 26 sort(ab, ab + top); 27 for (int d = 0; d < n; d++) { 28 for (int c = 0; c < n; c++) { 29 if (c == d) continue; 30 int pos = 31 lower_bound(ab, ab + top, node{num[d] - num[c], 0, 0}) - ab; 32 while (pos < top && ab[pos].add == num[d] - num[c]) { 33 if (ab[pos].a != c && ab[pos].a != d && ab[pos].b != c && 34 ab[pos].b != d) { 35 res = max(res, num[d]); 36 break; 37 } 38 pos++; 39 } 40 } 41 } 42 if (res == (ll)-1e15) 43 printf("no solution "); 44 else 45 printf("%lld ", res); 46 } 47 }
坐标离散化
AOJ 0531 一块W×H(W,H≤1e6)广告版,贴上了M(≤1e3)个胶带,问没有胶带的部分的连通块数量
1 #include <bits/stdc++.h> 2 using namespace std; 3 #define P pair<int, int> 4 5 const int MAX_N = 1005; 6 int w, h, n; 7 int X1[MAX_N], X2[MAX_N], Y1[MAX_N], Y2[MAX_N]; 8 int fld[6 * MAX_N][6 * MAX_N]; 9 int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; 10 11 int compress(int *xx1, int *xx2, int ww) { 12 vector<int> xs; 13 for (int i = 0; i < n; i++) { 14 for (int d = -1; d <= 1; d++) { 15 int tx1 = xx1[i] + d, tx2 = xx2[i] + d; 16 if (0 <= tx1 && tx1 < ww) xs.push_back(tx1); 17 if (0 <= tx2 && tx2 < ww) xs.push_back(tx2); 18 } 19 } 20 sort(xs.begin(), xs.end()); 21 xs.erase(unique(xs.begin(), xs.end()), xs.end()); 22 for (int i = 0; i < n; i++) { 23 xx1[i] = find(xs.begin(), xs.end(), xx1[i]) - xs.begin(); 24 xx2[i] = find(xs.begin(), xs.end(), xx2[i]) - xs.begin(); 25 } 26 return xs.size(); 27 } 28 29 int main() { 30 while (scanf("%d%d", &w, &h), w && h) { 31 scanf("%d", &n); 32 for (int i = 0; i < n; i++) { 33 scanf("%d%d%d%d", &X1[i], &Y1[i], &X2[i], &Y2[i]); 34 } 35 w = compress(X1, X2, w); 36 h = compress(Y1, Y2, h); 37 memset(fld, 0, sizeof(fld)); 38 for (int i = 0; i < n; i++) { 39 for (int x = X1[i]; x < X2[i]; x++) { 40 for (int y = Y1[i]; y < Y2[i]; y++) fld[x][y] = 1; 41 } 42 } 43 int ans = 0; 44 for (int y = 0; y < h; y++) { 45 for (int x = 0; x < w; x++) { 46 if (fld[x][y]) continue; 47 ans++; 48 queue<P> que; 49 que.push(P(x, y)); 50 while (!que.empty()) { 51 int sx = que.front().first, sy = que.front().second; 52 que.pop(); 53 for (int i = 0; i < 4; i++) { 54 int tx = sx + dx[i], ty = sy + dy[i]; 55 if (tx >= 0 && tx < w && ty >= 0 && ty < h && 56 fld[tx][ty] == 0) { 57 que.push(P(tx, ty)); 58 fld[tx][ty] = 1; 59 } 60 } 61 } 62 } 63 } 64 printf("%d ", ans); 65 } 66 }
END
以上是关于《挑战程序设计竞赛》课后练习题解集——3.2 常用技巧精选的主要内容,如果未能解决你的问题,请参考以下文章
《挑战程序设计竞赛》课后练习题解集——2.5 它们其实都是“图”
《挑战程序设计竞赛》课后练习题解集——2.3 记录结果再利用的“动态规划”
挑战程序设计竞赛3.2习题:Bound Found POJ - 2566