码迷,mamicode.com
首页 > 其他好文 > 详细

《挑战程序设计竞赛》课后练习题解集——3.2 常用技巧精选(一)

时间:2020-02-05 20:05:46      阅读:70      评论:0      收藏:0      [点我收藏+]

标签:include   cond   开区   这一   eof   lld   i++   ack   尺取法   

常用技巧精选(一)

尺取法

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\n", dis, z + 1, y);
38         }
39     }
40 }
View Code

 

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\n", res);
34     }
35 }
View Code

 

 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\n", 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 ? \n :  );
39     }
40 }
View Code

 

反转

关于书上例题 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 }
View Code

 

 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\n", 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 ? \n :  );
59         }
60     }
61 }
View Code

 

弹性碰撞

思维点主要在于,不关注具体碰撞过程,只需要结果坐标,再各小球顺序不会改变,就可以知道最后的状态

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\n");
30     else {
31         printf("YES\n");
32         inc(i, 1, n) printf("%d", col[i]);
33     }
34 }
View Code

 

 折半枚举

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\n", res.fi, res.se);
55     }
56 }
View Code

 

 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\n");
44         else
45             printf("%lld\n", res);
46     }
47 }
View Code

 

坐标离散化

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\n", ans);
65     }
66 }
View Code

 

END

《挑战程序设计竞赛》课后练习题解集——3.2 常用技巧精选(一)

标签:include   cond   开区   这一   eof   lld   i++   ack   尺取法   

原文地址:https://www.cnblogs.com/hs-zlq/p/12253813.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!