大白书 1.3节 高效算法设计举例
例题 17 UVA 11462
简单题,注意那个数值的范围是100以内就可以了。
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
const int MAXN = 100 + 5;
int cnt[MAXN];
int main()
{
int n;
while(scanf("%d", &n) != EOF && n){
memset(cnt, 0, sizeof(cnt));
int u;
for(int i = 1 ; i <= n ; i++){
scanf("%d", &u);
cnt[u]++;
}
int now = 1;
int f = 1;
while(now < MAXN){
while(cnt[now] == 0 && now < MAXN) now++;
if(now == MAXN){
printf("\n");
break;
}
if(f) f = 0;
else printf(" ");
printf("%d", now);
cnt[now]--;
}
}
return 0;
}
快速IO:
#include <cstdio>
#include <cstring>
#include <cctype>
inline int readint()
{
char c = getchar();
while(!isdigit(c)) c = getchar();
int x = 0;
while(isdigit(c)){
x = x * 10 + c - '0';
c = getchar();
}
return x;
}
int buf[10];
inline void writeint(int i){
int p = 0;
if(i == 0) p++;
else while(i){
buf[p++] = i % 10;
i /= 10;
}
for(int j = p - 1 ; j >= 0 ; j--) putchar('0' + buf[j]);
}
int main()
{
int n, x, c[101];
while(n = readint()){
memset(c, 0, sizeof(c));
for(int i = 0 ; i < n ; i++) c[readint()]++;
int first = 1;
for(int i = 1 ; i <= 100 ; i++){
for(int j = 0 ; j < c[i] ; j++){
if(!first) putchar(' ');
first = 0;
writeint(i);
}
}
putchar('\n');
}
return 0;
}
例题 18 UVA 11078
从前向后遍历,保存之前区间的一个最大值。
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <iostream>
using namespace std;
int main()
{
int T;
scanf("%d", &T);
while(T--){
int n, u;
scanf("%d", &n);
int mmax = -150000 * 2;
int ans = 0;
for(int i = 1 ; i <= n ; i++){
scanf("%d", &u);
if(i == 1) mmax = u;
else if(i == 2) ans = mmax - u, mmax = max(mmax, u);
else ans = max(ans, mmax - u), mmax = max(mmax, u);
}
printf("%d\n", ans);
}
return 0;
}
例题 19 UVA 11549
可以证明所有数的尾数最终都会变成0、1,、5、6中的一个,所以循环次数是优先的,直接暴力就可以。
白书介绍了一个floyd判圈法,主要节省了读取哪些数出现过的时间。
暴力法:
/* 暴力过法,注意UVA是lld就可以 */
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <map>
#include <set>
using namespace std;
#define LL long long
map<LL,LL>mm;
set<LL>s;
int buf[100];
//LL ne(LL n, LL k)
//{
//// printf("n = %I64d, k = %I64d\n", n, k);
// if(!k) return k;
// int L = 0;
// while(k){buf[L++] = k % 10, k /= 10;}
// if(n > L) n = L;
// LL ans = 0;
// for(int i = 0 ; i < n ; i++){
// ans = ans * 10 + buf[--L];
// }
//// printf("ans = %I64d\n", ans);
// return ans;
//}
int main()
{
int T;
scanf("%d", &T);
while(T--){
LL n, k;
scanf("%lld%lld", &n, &k);
// mm.clear();
s.clear();
LL ans = 0;
LL up = 1;
for(int i = 0 ; i < n ; i++) up *= (LL)10;
while(!s.count(k)){
// printf("k = %I64d\n", k);
// system("pause");
// mm[k] = 1;
s.insert(k);
ans = max(ans, k);
k = k * k;
while(k >= up) k /= 10;
// k = ne(n, k * k);
}
printf("%lld\n", ans);
}
return 0;
}
Floyd判圈法:
/* 因为最后都会走成一个环,所以 */
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <string>
#include <algorithm>
#include <iostream>
#include <vector>
#include <set>
using namespace std;
#define LL long long
#define gmax(a,b) ((a) > (b) ? (a) : (b))
LL ne(LL k, LL up)
{
while(k >= up) k /= 10;
return k;
}
int main()
{
int T;
scanf("%d", &T);
while(T--){
int n;
LL k;
scanf("%d%lld", &n, &k);
LL k1, k2;
LL up = 1;
while(n--) up *= (LL)10;
LL ans = k;
k1 = k;
k2 = ne(k * k, up); ans = gmax(ans, k2);
k2 = ne(k2 * k2, up); ans = gmax(ans, k2);
while(k1 != k2){
k1 = ne(k1 * k1, up);
k2 = ne(k2 * k2, up); ans = gmax(ans, k2);
k2 = ne(k2 * k2, up); ans = gmax(ans, k2);
// printf("k1 = %lld, k2 = %lld\n")
}
printf("%lld\n", ans);
}
return 0;
}
例题 20 UVA 1398
记录每个流星在矩形的区间,然后扫描法做就可以。
值得注意的是流星碰撞矩形的时间区间应该怎么去算的问题,需要自己分类讨论一下。
特别注意和矩形没有碰撞的情况、以及边界上的点不能算的问题。
/* 简单扫描法,只需要分清左事件和右事件就可以了。 关于怎么处理进入矩形的时间 分别在x方向求与左右边界相交的时间,在y方向求与上下边界相交的时间 那么,这个进入和离开值分别是这四个时间的两个中间值,具体可以用物理中速度分解来理解 注意只有当第三大的时间值大于等于0时才有解,并要求所有的合法时间大于0,详见代码注释 */
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <string>
#include <iterator>
#include <set>
using namespace std;
#define gmax(a,b) ((a) > (b) ? (a) : (b))
#define gmin(a,b) ((a) < (b) ? (a) : (b))
const int MAXN = 100000 + 5;
struct D
{
int x, y;
int a, b;
}d[MAXN];
struct E
{
double t;
int u;
int whi;
bool operator < (const E &rbs)const{
if(t != rbs.t) return t < rbs.t;
return u < rbs.u; ///因为球在边界也算,所以优先返回右事件
}
E(){}
E(double _t, int _u, int _w){t = _t, u = _u, whi = _w;}
}e[MAXN * 2];
bool cmp(E a, E b)
{
if(a.t == b.t) return a.u < b.u;
return a.t < b.t;
}
multiset<E>s;
multiset<E>::iterator it;
int w, h;
int n;
double t[10];
int main()
{
// freopen("UVA 1398.in", "r", stdin);
int T;
scanf("%d", &T);
while(T--){
scanf("%d%d%d", &w, &h, &n);
for(int i = 0 ; i < n ; i++) scanf("%d%d%d%d", &d[i].x, &d[i].y, &d[i].a, &d[i].b);
s.clear();
int cnt = 0;
for(int i = 0 ; i < n ; i++){
if(d[i].a == 0 && (d[i].x >= w || d[i].x <= 0)) continue;
if(d[i].b == 0 && (d[i].y >= h || d[i].y <= 0)) continue;
if(d[i].a > 0){
t[1] = (double)(-1.0 * d[i].x) / (1.0 * d[i].a); ///分别为四个边界碰撞时间
t[2] = (double)(w - 1.0 * d[i].x) / (1.0 * d[i].a);
}
else if(d[i].a < 0){
t[1] = (double)(w - 1.0 * d[i].x) / (1.0 * d[i].a);
t[2] = (double)(-1.0 * d[i].x) / (1.0 * d[i].a);
}
else t[1] = -1e9, t[2] = 1e9;
if(d[i].b > 0){
t[3] = (double)(-1.0 * d[i].y) / (1.0 * d[i].b);
t[4] = (double)(h - 1.0 * d[i].y) / (1.0 * d[i].b);
}
else if(d[i].b < 0){
t[3] = (double)(h - 1.0 * d[i].y) / (1.0 * d[i].b);
t[4] = (double)(-1.0 * d[i].y) / (1.0 * d[i].b);
}
else t[3] = -1e9, t[4] = 1e9;
// printf("for i = %d, t1 = %f, t2 = %f\n", i, t[2], t[3]);
double t1 = gmax(0, gmax(t[1], t[3]));
double t2 = gmin(t[2], t[4]);
// if(i == 6){
// printf("i = %d, t1 = %f , t2 = %f\n", i + 1, t1, t2);
// printf(" t[1] = %f, t[2] = %f, t[3] = %f, t[4] = %f\n", t[1], t[2], t[3], t[4]);
// }
if(t2 > t1){
// e[cnt++] = E(t1, 1, i + 1);
// e[cnt++] = E(t2, 0, i + 1);
s.insert(E(t1, 1, i + 1)); ///注意此处max
s.insert(E(t2, 0, i + 1));
}
}
int ans = 0;
// int cnt = 0;
// if(s.empty()){
// printf("0\n");
// continue;
// }
for(it = s.begin() ; it != s.end(); it++){
E temp = *it;
// printf("t = %f, while u = %d, cnt = %d, num = %d\n", temp.t, temp.u, cnt, temp.whi);
if(temp.u == 0) cnt--;
else cnt++;
ans = max(ans, cnt);
if(it == s.end()) break;
}
// int num = 0;
// sort(e, e + cnt, cmp);
// for(int i = 0 ; i < cnt ; i++){
// if(e[i].u == 0) num--;
// else num++;
// ans = gmax(ans, num);
// }
printf("%d\n", ans);
}
return 0;
例题 21 UVA 1121
很擅长的题就不说了。
/* 开两个指针,对于当前点,找到最近的一个地方即可。 因为都是正数,所以sum值随着左端点右移递减。 如果不都是正数就不存在这个做法了。 */
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <string>
using namespace std;
#define LL long long
const int MAXN = 1e5 + 5;
int a[MAXN];
int main()
{
int n;
while(scanf("%d", &n) != EOF){
LL s;
scanf("%lld", &s);
int l = 0;
int ans = n + 1;
LL sum = 0;
for(int i = 1 ; i <= n ; i++){
scanf("%d", &a[i]);
sum += a[i];
int ok = 0;
while(l < i && sum - a[l] >= s){
ok = 1;
sum -= a[l++];
}
// printf("i = %d, l = %d\n", i, l);
if(ok) ans = min(ans, i - l + 1);
}
printf("%d\n", ans);
}
return 0;
}
~例题 22 UVA 1330
刚开始很自然的每个点找一个最优解,结果发现只能穷举的做。
题解是每个点取最高点,然后左右边界能遍历到最远处来做。这样能保证遍历到所有可能的最优子矩阵。
/* up,l,r函数分别表示向上遍历到的点、在保证取能取到的最上面的点时形成矩形的左边界和右边界 l和r数组的更新注意一下,已在代码中注释 */
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <iostream>
using namespace std;
const int MAXN = 1000 + 5;
int g[MAXN][MAXN];
char op[5];
int l[MAXN][MAXN], r[MAXN][MAXN];
int up[MAXN][MAXN];
int main()
{
int T;
scanf("%d", &T);
while(T--){
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1 ; i <= n ; i++){
for(int j = 1 ; j <= m ; j++){
scanf("%s", op);
if(op[0] == 'F') g[i][j] = 1;
else g[i][j] = 0;
}
}
memset(l, 0, sizeof(l));
memset(r, 0, sizeof(r));
memset(up, 0, sizeof(up));
int ans = 0;
for(int i = 1 ; i <= n ; i++){
for(int j = 1 ; j <= m ; j++){
if(g[i][j] == 0) up[i][j] = 0, l[i][j] = 0;
else up[i][j] = up[i - 1][j] + 1, l[i][j] = l[i][j - 1] + 1;
}
for(int j = m ; j >= 1 ; j--){
if(g[i][j] == 0) r[i][j] = 0;
else r[i][j] = r[i][j + 1] + 1;
}
for(int j = 1 ; j <= m ; j++){
if(g[i][j] == 0) continue;
if(i == 1) ans = max(ans, r[i][j] + l[i][j] - 1);
else{
int x = i + 1 - up[i][j];
int y = j;
// if(i == 2 && j == 2) printf("x = %d, y = %d\n", x, y);
if(up[i][j] != 1){
l[i][j] = min(l[i][j], l[i - 1][y]); ///不能用max(l[i][j], l[x][y]),l[i-1][j]是已经形成的能构成大矩形的最大左边界
r[i][j] = min(r[i][j], r[i - 1][y]); ///因为l[i-1][j]和l[i][j]的最小值才是确保这个矩形左右边界的存在
}
ans = max(ans, (r[i][j] + l[i][j] - 1) * up[i][j]);
}
}
}
// printf("///***l\n");
// for(int i = 1 ; i <= n ; i++){
// for(int j = 1 ; j <= m ; j++) printf("%d ", l[i][j]);
// printf("\n");
// }
// printf("***///\n");
printf("%d\n", ans * 3);
}
return 0;
}
~例题 23 UVA 1382
首先离散化,最粗陋的做法是O(n^4)的,即n^3的枚举,n的检测。
题解用最长连续子序列和来做,枚举上边界和下边界后,再从左往右枚举右边界,并在途中保存最优的左边界。
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <string>
#include <algorithm>
#include <iostream>
#include <map>
#include <vector>
using namespace std;
const int MAXN = 100 + 5;
struct D
{
int x, y;
}d[MAXN];
bool cmp1(D a, D b){return a.x < b.x;}
bool cmp2(D a, D b){return a.y < b.y;}
map<int,int>mx, my;
int cntx, cnty;
int g[MAXN][MAXN];
int numx[MAXN][MAXN], numy[MAXN][MAXN];
int main()
{
// freopen("UVA 1382.in", "r", stdin);
int n;
int cas = 0;
while(scanf("%d", &n) != EOF && n){
mx.clear();
my.clear();
cntx = cnty = 0;
int u, v;
memset(g, 0, sizeof(g));
for(int i = 0 ; i < n ; i++) scanf("%d%d", &d[i].x, &d[i].y);
sort(d, d + n, cmp1);
for(int i = 0 ; i < n ; i++) if(mx[d[i].x] == 0) mx[d[i].x] = ++cntx;
sort(d, d + n, cmp2);
for(int i = 0 ; i < n ; i++) if(my[d[i].y] == 0) my[d[i].y] = ++cnty;
for(int i = 0 ; i < n ; i++) g[mx[d[i].x]][my[d[i].y]]++;
if(cntx == 1 || cnty == 1){
printf("Case %d: %d\n", ++cas, n);
continue;
}
memset(numx, 0, sizeof(numx));
memset(numy, 0, sizeof(numy));
for(int i = 1 ; i <= cntx ; i++){
for(int j = 1 ; j <= cnty ; j++){
numx[i][j] = numx[i - 1][j];
numy[i][j] = numy[i][j - 1];
if(g[i][j]) numx[i][j]+=g[i][j], numy[i][j]+=g[i][j];
}
}
// for(int i = 1 ; i <= cntx ; i++){
// for(int j = 1 ; j <= cnty ; j++){
// printf("%d ", g[i][j]);
// }
// printf("\n");
// }
int ans = 0;
for(int i = 1 ; i <= cntx; i++){
for(int j = i + 1 ; j <= cntx; j++){
int mmax = -1e9;
int sum = 0;
// int re = -1;
for(int k = 1 ; k <= cnty; k++){
int up = numx[j - 1][k] - numx[i][k];
int divise = 0;
if(g[i][k - 1]) sum+=g[i][k - 1]; if(g[j][k - 1]) sum+=g[j][k - 1];
if(g[i][k]) divise+=g[i][k]; if(g[j][k]) divise+=g[j][k];
// if(i == 1 && j == 9){
// printf("i = %d, j = %d, k = %d, re = %d\n", i, j, k, re);
// printf("up = %d, sum = %d, mmax = %d\n", up, sum, mmax);
// }
ans = max(ans, sum + mmax + up + divise);
// if(mmax < up - sum) re = k;
mmax = max(mmax, up - sum);
// if(i == 1 && j == 2)
// printf("i = %d, j = %d, k = %d, ans = %d, mmax = %d, sum = %d, up = %d\n", i, j, k, ans, mmax, sum, up);
}
}
}
printf("Case %d: %d\n", ++cas, ans);
}
return 0;
}
例题 24 UVA 10775
枚举选的子矩阵的两个坐标,然后在第三个坐标上做最大连续子序列和。
/*
简单的dp计数,只要把三维的转化成一维的就可以
注意ans初始化的问题,不能直接初始化为0.
*/
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <string>
using namespace std;
#define LL long long
#define gmax(a,b) ((a) > (b) ? (a) : (b))
const int MAXN = 20 + 5;
LL g[MAXN][MAXN][MAXN];
LL sum[MAXN][MAXN][MAXN];
LL dp[MAXN];
int main()
{
int T;
scanf("%d", &T);
while(T--){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
LL ans = 0;
for(int i = 1 ; i <= a ; i++){
for(int j = 1 ; j <= b ; j++){
for(int k = 1 ; k <= c ; k++){
scanf("%lld", &g[i][j][k]);
ans = min(ans, g[i][j][k]);
}
}
}
memset(sum, 0, sizeof(sum));
for(int i = 1 ; i <= a ; i++){
for(int j = 1 ; j <= b ; j++){
for(int k = 1 ; k <= c ; k++){
sum[i][j][k] = sum[i][j][k - 1] + g[i][j][k];
// sum[i][j][k] = sum[i][j][k - 1] + g[i][j][k] + sum[i][j - 1][k] - sum[i][j - 1][k - 1];
}
for(int k = 1 ; k <= c ; k++){
sum[i][j][k] += sum[i][j - 1][k];
}
}
}
for(int x1 = 1 ; x1 <= b ; x1++){
for(int x2 = x1 ; x2 <= b ; x2++){
for(int y1 = 1 ; y1 <= c ; y1++){
for(int y2 = y1 ; y2 <= c ; y2++){
dp[0] = 0;
for(int i = 1 ; i <= a ; i++){
dp[i] = (sum[i][x2][y2] - sum[i][x2][y1 - 1] - sum[i][x1 - 1][y2] + sum[i][x1 - 1][y1 - 1]);
dp[i] += gmax(0, dp[i - 1]);
ans = gmax(ans, dp[i]);
}
}
}
}
}
printf("%lld\n", ans);
if(T != 0)
printf("\n");
}
return 0;
}
~例题 25 UVA 1326
每个字母出现偶数次,字母最多26种,所以自然异或和,并且用状压表示。
但是字符串数为24个刚好不能状压。
题解采取中途相遇法,即把字符串分成两部分。第一部分状压得到一些数,第二部分状压得到的在第一部分得到的数中间找有没有相等的数。
/*
转化后就成了n个二进制表示的数,如何取最多的数使得异或和为0.
注意题目数据没有多解。
因为a^b == 0 -> a == b
书上题解给出中途相遇法,即把数分成两部分,前n/2个用状压的方式求出所有能产生的异或和值
后n/2个状压得出值去和前面比对,找出最大的一个就可以。
写的时候写残。分成两组时123456 -> 456 123,状压表示的时候会有些麻烦导致下标错误。
*/
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
using namespace std;
const int MAXN = 30;
int g[MAXN][MAXN];
int a[MAXN];
char op[MAXN];
struct D
{
int u, v;
int cnt;
}d[1 << (MAXN / 2)];
bool cmp(D a, D b)
{
if(a.v == b.v && a.cnt == b.cnt) return a.u < b.u;
else if(a.v == b.v) return a.cnt < b.cnt;
return a.v < b.v;
}
int main()
{
// freopen("UVA 1326.in", "r", stdin);
int n;
while(scanf("%d", &n) != EOF){
memset(g, 0, sizeof(g));
for(int i = 0 ; i < n ; i++){
scanf("%s", op);
for(int j = 0 ; j < (int)strlen(op) ; j++){
g[i][op[j] - 'A'] ^= 1;
}
a[i] = 0;
for(int j = 0 ; j < 26 ; j++) a[i] = (a[i] << 1) + g[i][j];
// printf("a[%d] = %d\n", i, a[i]);
}
int ans = 0, out = 0;
int L = n / 2, R = n - n / 2;
for(int i = 0 ; i < (1 << L) ; i++){
d[i].u = i;
d[i].v = 0;
d[i].cnt = 0;
for(int j = 0 ; j < L ; j++){
if(i & (1 << j)) d[i].v ^= a[j], d[i].cnt++;
}
}
// for(int i = 0 ; i < n ; i++) printf("a[%d] = %d\n", i, a[i]);
// printf("ans = %d, out = %d\n", ans, out);
sort(d, d + (1 << L), cmp);
// for(int i = 0 ; i < (1 << L) ; i++)
// printf("i = %d, u = %d, v = %d, cnt = %d\n", i, d[i].u, d[i].v, d[i].cnt);
for(int j = 0 ; j < R ; j++) a[j] = a[j + L];
for(int i = 0 ; i < (1 << R) ; i++){
int v = 0;
int cnt = 0;
for(int j = 0 ; j < R ; j++){
if(i & (1 << j)) v ^= a[j], cnt++;
}
int le = 0, re = (1 << L) - 1;
while(le < re - 1){
int mid = (le + re) >> 1;
if(d[mid].v <= v) le = mid;
else re = mid;
}
if(v == 0){
if(ans < cnt)
ans = cnt, out = i;
// else if(ans == cnt && out < i) out = i;
}
int mark = -1;
if(d[re].v == v) mark = re;
else if(d[le].v == v) mark = le;
// if(i == 6 || i == 7){
// printf("i = %d, v = %d, cnt = %d\n", i, v, cnt);
// printf("mark = %d, u = %d, v = %d, cnt = %d\n", mark, d[mark].u, d[mark].v, d[mark].cnt);
// }
if(mark == -1) continue;
if(d[mark].v == v){
if(ans < d[mark].cnt + cnt) ans = d[mark].cnt + cnt, out = ((d[mark].u << R) | i);
// else if(ans == d[mark].cnt + cnt && out < (d[mark].u << L) | i) out =( (d[mark].u << L )| i);
}
// if(i == 6){
// printf("v = %d, cnt = %d\n", v, cnt);
// }
}
if(ans == 0) printf("0\n\n");
else{
printf("%d\n", ans);
int res[MAXN];
int cnt = 0;
for(int i = 0 ; i < n ; i++){
if((1 << i) & out){
if(i >= R) res[cnt++] = i - R + 1;
else res[cnt++] = i + L + 1;
}
}
sort(res, res + cnt);
for(int i = 0 ; i < cnt ; i++){
printf("%d", res[i]);
if(i == cnt - 1) printf("\n");
else printf(" ");
}
}
}
return 0;
}