2021csp 赛前第八次集训 - 其它算法

这篇博客回顾了2021年CSP比赛前的第八次集训,重点讨论了高精度算法在解决Hanoi双塔问题和麦森数中的应用,以及二分法在跳石头问题中的运用。此外,还介绍了贪心策略在推销员问题和花生采摘中的优化,以及枚举法在求和与魔法阵问题的解决方案。

高精度

2007年普及组T4 Hanoi双塔问题
  • 1−>21 -> 21>2
    2−>62 -> 62>6

    n−>(2n−1)∗2=2n+1−2n -> (2^n-1)*2 = 2^{n+1}-2n>(2n1)2=2n+12
  • n<=200,答案的最大值是2201−2=2∗102420−2,约等于2∗1060−2,是一个61位数n <= 200,答案的最大值是2^{201}-2 = 2 * 1024^{20} - 2, 约等于2 * 10^{60} - 2,是一个61位数n<=20022012=2102420221060261
  • 实际上,2的次幂的个位数一定>=2,可以直接用个位数减2实际上,2的次幂的个位数一定>=2,可以直接用个位数减22>=22
#include <bits/stdc++.h>
using namespace std;

int n, t, a[100] = {0, 1};

int main() {
//	freopen("hanoi.in", "r", stdin);
//	freopen("hanoi.out", "w", stdout);
	cin >> n;
	
	//t表示进位 
	for (int i = 1; i <= n + 1; i ++) {
		t = 0;			
		for (int j = 1; j <= 61; j ++) {
			t += a[j] * 2;
			a[j] = t % 10;
			t /= 10;
		}
	}
	
	//t表示借位 
	t = 2;
	for (int i = 1; i <= 61; i ++) {
		t = a[i] - t;
		if (t < 0) t = 1, a[i] = t + 10;
		else {
			a[i] = t;
			break;
		}
	}
	
	//去掉前导0 
	for (int i = 61; i >= 1; i --) {
		if (a[i] != 0) {
			t = i;
			break;
		}
	}
	for (int i = t; i >= 1; i --) cout << a[i];

	return 0;
}
2003年普及组T4 麦森数
  • 如果10(k−1)<2p<10k,则2p−1是一个k位数如果10^{(k-1)} < 2^p < 10^k, 则2^p-1是一个k位数10(k1)<2p<10k,2p1k
  • p∗log10(2)<k<p∗log10(2)+1p*log10(2) < k < p * log10(2) + 1plog10(2)<k<plog10(2)+1
  • 用暴力方法,时间复杂度O(500∗P),超时用暴力方法,时间复杂度O(500*P),超时O(500P)
#include <bits/stdc++.h>
using namespace std;

int p, k, ans[510] = {1}, len = 1;

int main() {
//	freopen("mason.in", "r", stdin);
//	freopen("mason.out", "w", stdout);

	cin >> p;
	k = p * log10(2) + 1;
	cout << k << endl;
	
	for (int i = 1; i <= p; i ++) {
		int t = 0;
		for (int j = 0; j < 500; j ++) {
			t += ans[j] * 2;
			ans[j] = t % 10;
			t = t / 10;
		}
	}

	ans[0] -= 1;	
	for (int i = 499; i >= 0; i --) {
		cout << ans[i];
		if (i % 50 == 0) cout << endl;
	}
	
	return 0;
}
  • 用快速幂算法,时间复杂度O(5002∗logP)用快速幂算法,时间复杂度O(500^2*logP)O(5002logP)
#include <bits/stdc++.h>
using namespace std;

const int N = 510;
int p, k;
int base[N], ans[N], tmp[N];

//a * b -> c
void mul(int a[], int b[], int c[]) {
	memset(tmp, 0, sizeof(tmp));
	
	for (int i = 0; i < 500; i ++) {
		for (int j = 0; j < 500; j ++) {
			if (i + j < 500) {
				tmp[i + j] += a[i] * b[j];
			}
		}
	}

	int t = 0;
	for (int i = 0; i < 500; i ++) {
		t += tmp[i];
		c[i] = t % 10;
		t = t / 10;
	}
}

int main() {
//	freopen("mason.in", "r", stdin);
//	freopen("mason.out", "w", stdout);

	cin >> p;
	k = p * log10(2) + 1;
	cout << k << endl;

	ans[0] = 1;
	base[0] = 2;
	while (p) {
		if (p & 1) mul(ans, base, ans);
		mul(base, base, base);  
		p >>= 1;
	}

	ans[0] -= 1;
	for (int i = 499; i >= 0; i --) {
		cout << ans[i];
		if (i % 50 == 0) cout << endl;
	}
	
	return 0;
}

二分

2015年提高组D2T1 跳石头
#include <bits/stdc++.h>
using namespace std;

const int N = 5e4 + 10;
int len, n, m; 
int d[N];

//返回当最短跳跃距离是x时,应该移走的岩石数量 
int check(int x) {
	int res = 0, pos = 0;		//起点 
	for (int i = 1; i <= n; i ++) {
		if (d[i] - pos < x) res ++;
		else pos = d[i];
	}
	return res;
}

int main() {
//	freopen("stone.in", "r", stdin);
//	freopen("stone.out", "w", stdout);

	scanf("%d%d%d", &len, &n, &m);
	for (int i = 1; i <= n; i ++) scanf("%d", &d[i]);
	d[++ n] = len;
	
	int l = 0, r = len, ans;
	while (l <= r) {
		int mid = l + r >> 1;
		if (check(mid) <= m) {
			ans = mid;
			l = mid + 1;
		}
		else {
			r = mid - 1;
		}
	}
	printf("%d\n", ans);

	return 0;
}

贪心,优先队列优化

2004年普及组T2 花生采摘
#include <bits/stdc++.h>
using namespace std;

const int N = 30;
int m, n, k, t, cnt;

struct node{
    int x, y, num;
};
bool operator < (node x, node y) {
    return x.num < y.num;
}
priority_queue <node, vector<node>, less<node> > q;

int main() {
//	freopen("mason.in", "r", stdin);
//	freopen("mason.out", "w", stdout);

    cin >> m >> n >> k;
    for (int i = 1; i <= m; i ++) {
        for (int j = 1; j <= n; j ++) {
            int p;
            cin >> p;
            node t = {i, j, p};          
            q.push(t);                                      //优先队列堆顶的花生数量最多
        }
    }

    node pre, now;
    for (int i = 1; ; i ++) {
        now = q.top();
        if (i == 1) pre = {0, now.y, 0};                    //从第0行第now.y列出发 

        t += abs(now.x - pre.x) + abs(now.y - pre.y) + 1;   //曼哈顿距离 + 采摘时间 
        if (t + now.x <= k) cnt += now.num;                 //如果来得及回去就采 
        else break;                                         //否则就回去 

        pre = now;
        q.pop();

        if (q.empty()) break;                               //采完了就回去 
    }
    cout << cnt << endl;

    return 0;
}
2015年普及组T4 推销员
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;    

const int N = 1e5 + 10;
int n, s[N], a[N], t[N], h[N];          //路程,疲劳值,total, 往右找 t 值最高的人家的编号

priority_queue < pair<int, int> > q;    //first:疲劳值,second:i 

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++) scanf("%d", &s[i]);
    for (int i = 1; i <= n; i ++) {
        scanf("%d", &a[i]);
        t[i] = a[i] + 2 * s[i];
    }

    int k = n;
    for (int i = n - 1; i >= 0; i --) {
        h[i] = k;                       //第 i 户人家右侧 t 值最高的人家的编号是 h[i] 
        if (t[i] > t[k]) k = i;
    }

    int ans = 0;                        //已经推销了x-1户人家,总疲劳值为 ans
    int lst = 0;                        //已经推销过的人家中最远的一家    
    int l, r;
    for (int x = 1; x <= n; x ++) {
        if (q.size()) l = q.top().second;           //第 x 户人家左侧未推销的人家中疲劳值最大的人家的编号 
        r = h[lst];

        if (q.size() && a[l] < a[r] + 2 * (s[r] - s[lst]) || !q.size()) {
            ans += a[r] + 2 * (s[r] - s[lst]);      //去编号为 r 的人家推销 
            for (int i = lst + 1; i < r; i ++) {    //将左侧的人家加入优先队列 
                q.push({a[i], i});                  
            } 
            lst = r;
        }
        else {                                      //去编号为 l 的人家推销 
            ans += a[l];
            q.pop();
        }
        printf("%d\n", ans);
    }

    return 0;
}

枚举

2015年普及组T3 求和
  • 枚举,70分
#include <iostream>
#include <cstdio>
#include <vector>	
using namespace std;

const int N = 1e5 + 10, M = 1e5 + 10;
int n, m, num[N];
vector <int> col[M];	//有m种颜色,第 i种颜色的数量为 col[i].size() 

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i ++) scanf("%d", &num[i]);
	for (int i = 1; i <= n; i ++) {
		int c;
		scanf("%d", &c);		//某种颜色
		col[c].push_back(i);	//将每个格子的编号存储到对应颜色的数组里 
	}

	//编号之和 * 数字之和有可能大于 MAX_INT 
	long long ans = 0;
	for (int i = 1; i <= m; i ++) {					//枚举第 i 种颜色 
		for (int j = 0; j < col[i].size(); j ++ ) {			//枚举编号为 x的格子
			for (int k = j + 1; k < col[i].size(); k ++) {	//枚举编号为 y的格子
				int x = col[i][j];
		 		int z = col[i][k];
				if (x % 2 == z % 2) {
					ans += (long long)(x + z) * (num[x] + num[z]);	
					ans %= 10007;
				}
			}
		}
	}
	printf("%lld\n", ans);
	
	return 0;
}
2016年普及组T4 魔法阵
  • 枚举,60分
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int N = 15010, M = 40010;
int n, m;
int x[M], ans[M][5];

int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= m; i ++) {
		int a;
		scanf("%d", &x[i]);
	}

	for (int a = 1; a <= m; a ++) {
		for (int b = 1; b <= m; b ++) {
			if (x[b] <= x[a]) continue;
			for (int c = 1; c <= m; c ++) {
				if (x[c] <= x[b]) continue;
				if ((x[b] - x[a]) % 2 == 1) continue;
				if ( 3 * (x[b] - x[a]) >= (x[c] - x[b]) ) continue;
				for (int d = 1; d <= m; d ++) {
					if (x[b] - x[a] == 2 * (x[d] - x[c]) ) {
						ans[a][1] ++;
						ans[b][2] ++;
						ans[c][3] ++;
						ans[d][4] ++;
					}
				}
			}
		}
	}

	for (int i = 1; i <= m; i ++) {
		for (int j = 1; j <= 4; j ++) {
			printf("%d ", ans[i][j]);
		}
		printf("\n");
	}
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值