《挑战程序设计竞赛》课后练习题解集——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\n", 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\n", 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\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 }

反转

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

弹性碰撞

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

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 }

折半枚举

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 }

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 }

坐标离散化

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 }

END

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

时间: 2024-08-03 23:02:32

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

《挑战程序设计竞赛》课后练习题解集——3.4 熟练掌握动态规划

<挑战程序设计竞赛>课后练习题解集——3.4 熟练掌握动态规划 状态压缩DP POJ 2441  有N头牛,M个槽,N,M≤20,每头牛只在指定的pi个槽里进食,不与其他牛共享槽.问有多少种分配方案. dp[i][S],当前第i头牛要进食,槽的使用状态为S 1 #include <cstdio> 2 #include <cstring> 3 #include <vector> 4 using namespace std; 5 6 int n, m; 7 in

[转] AOJ 0525 Osenbei《挑战程序设计竞赛(第2版)》练习题答案

来自 码农场 ? AOJ 0525 Osenbei<挑战程序设计竞赛(第2版)>练习题答案 只把代码复制过来,原博的其他分析请看链接. 1 #include <iostream> 2 #include <bitset> 3 #include <algorithm> 4 5 using namespace std; 6 7 bitset<10000> cookie[10]; 8 9 ///////////////////////////SubMai

POJ 3420 Quad Tiling 题解 《挑战程序设计竞赛》

POJ 3420 Quad Tiling贴瓷砖:4*N的地板上用2*1的瓷砖铺满,求所有方案数对M求余.3.4熟练掌握动态规划矩阵的幂久违地上了节课,太无聊,只好刷一题.假设S[n]表示填满n时的方案数,有S[0]=1.定义矩阵M[p][q] := 边缘p和边缘q可以拼合时取1,否则取0所谓的可以拼合表示,两个边缘拼起来后长度为1(为2就拼接不起来了),且内部缝隙可以用2*1的瓷砖填满.那么M就有一些简单的性质了,比如M的第一行应该是:0 0 0 0 0 0... 继续阅读:码农场 » POJ

POJ 3411 Paid Roads 题解 《挑战程序设计竞赛》

POJ 3411 Paid Roads开路:N个城市间有m条单向路,分别从a到b,可以在c处交P路费,也可以直接交R路费.那么问题来了,你的挖掘机怎么开最省钱?3.4熟练掌握动态规划状态压缩DP乍一看可以Dijkstra,实际上的确可以Dijkstra.不过多了一个预交费的c,所以在遍历的时候多了一维状态,这个维度储存当前走过的城市集合.在Dijkstra的时候,如果走过了c那么就有两个选择,选其中最省的即可:否则没得选.#include <iostream> #include&nb.

字符串HASH模板 取自挑战程序设计竞赛(第2版)

/*===================================================* 从b串中寻找和a串长度相同的子串,返回开始位置 不保证绝对正确,发生冲突概率为O(sqrt(n)), n为哈希函数的最大值 \*===================================================*/ #define ull unsigned long long const ull B = 1e8+7; /*according to the book*/

POJ 2836 Rectangular Covering 题解 《挑战程序设计竞赛》

POJ 2836 Rectangular Covering铺地板:坐标平面上有n各点,用任意大小(非零)的地板砖覆盖它们,求最省的地板砖总面积.3.4熟练掌握动态规划状态压缩DP先预处理数据,将n个点两两组合形成n * (n-1) / 2个矩形,计算每个矩形的面积和内部点个数.接着利用预处理数据来枚举,定义dp[S] := 矩形集为S时的最省面积先假设平面上没有矩形,那么dp[0]=0,接着一个一个地往平面上加矩形,递推关系是:dp[新矩形集合] = min(dp[新矩形集合], dp[旧矩形集

poj2431 Expedition (优先队列) 挑战程序设计竞赛

题目链接:http://poj.org/problem?id=2431 Expedition Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 9148   Accepted: 2673 Description A group of cows grabbed a truck and ventured on an expedition deep into the jungle. Being rather poor driver

Aizu 2249Road Construction 单源最短路变形《挑战程序设计竞赛》模板题

King Mercer is the king of ACM kingdom. There are one capital and some cities in his kingdom. Amazingly, there are no roads in the kingdom now. Recently, he planned to construct roads between the capital and the cities, but it turned out that the con

38天刷完挑战程序设计竞赛&amp;数论概论计划

现在是2017年12月6日22:03:36,在做数论概论的chapter6线性方程与最大公因数的习题时,粗略地翻了一下挑战程序设计竞赛,感觉相见恨晚:遂决定38天刷完挑战程序设计竞赛和数论概论. 时间:2017年12月7日00:00:00 -- 2018年1月15日00:00:00 目前进度:数论概论完成前6章,挑战程序设计竞赛完成0 目标:刷完数论概论44章,理解所有定理和证明,刷完全部练习 刷完挑战程序设计竞赛4章共406页,理解所有知识点和证明,刷完全部例题和习题(此目标可以调整,比如跳过