2022牛客寒假算法基础集训营1

A. 九小时九个人九扇门

题意:给定n个数,求这个n个数中任意个数的和的数字根(1~9),求所有方案下各个数字根的数量。
题解:首先一个数的数字根等于这个数对9取余的结果,本题为0-1背包的变形,f[i][j]表示在考虑前i个数情况下数字根为j的方案数量。
代码:
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10, mod = 998244353;
LL f[N][9]; 
int a[N];
int n;
int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i ++ ) {
		scanf("%d", &a[i]);
	}
	f[0][0] = 1;
	for (int i = 1; i <= n; i ++ ) {
		for (int j = 0; j < 9; j ++ ) {
			f[i][(j + a[i]) % 9] = (f[i - 1][j] + f[i - 1][(j + a[i]) % 9]) % mod;
		}
	}
	for (int i = 1; i < 9; i ++ ) {
        printf("%lld ", f[n][i]);
	}
    printf("%lld\n", f[n][0] - 1);
	return 0;
} 

C. Baby’s first attempt on CPU

题意:给定n句话,其中可能有某些个两句话之间有冲突,解决冲突的方法就是在这两句话之间插入空语句,使得这两句话之间至少3句话。
题解:由于数据量不大直接模拟一遍就好,先遍历一遍找见所有有冲突的话和其前面与其距离最近的冲突的话,然后模拟一遍插入空语句的过程,若发现冲突未解决则插入空语句,若发现冲突已经解决则直接插入这句话。
代码:
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int a[N];
int f[N * 5], cnt;
int main() {
    int n;
	scanf("%d", &n);
	int x[4];
	for (int i = 1; i <= n; i ++ ) {
		for (int j = 1; j <= 3; j ++ ) {
			scanf("%d", &x[j]);
		}
		for (int j = 1; j <= 3; j ++ ) {
			if (x[j]) {
				a[i] = i - j;
				break;
			}
		}
	}
	int res = 0;
	for (int i = 1; i <= n; i ++ ) { 
		if (a[i]) {
			int j = 1;
			for (; j <= 3; j ++ ) {
				if (f[cnt - j] == a[i])	{
					break;
				}
			}
			if (j <= 3) {
				int k = 4 - j;
				while (k -- ) {
					res ++ ;
					cnt ++ ;
				}
			}
		} 
		f[cnt ++ ] = i;
	}
	printf("%d\n", res);
    return 0;
}

D. 牛牛做数论

题意:给定一个公式 H ( x ) = f ( x ) x H(x) = \frac{f(x)}{x} H(x)=xf(x) f ( x ) f(x) f(x) x x x的欧拉函数,求2 ~ n中 H ( x ) H(x) H(x)的最大值和最小值对应的x。
题解: H ( x ) = ( 1 − 1 p 1 ) ( 1 − 1 p 2 ) . . . ( 1 − 1 p n ) H(x)=(1- \frac{1}{p_1})(1- \frac{1}{p_2})...(1- \frac{1}{p_n}) H(x)=(1p11)(1p21)...(1pn1) (p为x的质约数),因此要使 H ( x ) H(x) H(x)最大,就是找到第一个小于等于x的质数,最小就是求前k的质数的乘积不超过n的最大值。
代码:
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1000;
int primes[200], cnt;
bool st[N];

void init() {
	for (int i = 2; i <= N; i ++ ) {
		if (!st[i]) {
			primes[cnt ++ ] = i;
		}
		for (int j = 0; primes[j] <= N / i; j ++ ) {
			st[primes[j] * i] = 1;
			if (i % primes[j] == 0) {
				break;
			}
		}
	}
}

int main() {
	init();
	int t;
	scanf("%d", &t);
	while (t -- ) {
		int n;
		scanf("%d", &n);
		if (n == 1) {
			puts("-1");
			continue;
		}
		LL res = 1;
		for (int i = 0; i < cnt; i ++ ) {
			if (res * primes[i] <= n) {
				res *= primes[i];
			} else {
				break;
			}
		}
		printf("%lld ", res);
		for (int i = n; ; i -- ) {
			bool f = 1;
			for (int j = 2; j <= i / j; j ++ ) {
				if (i % j == 0) {
					f = 0;
					break;
				}
			}
			if (f) {
				printf("%d\n", i);
				break;
			} 
		}
	}
	return 0;
} 

E. 炸鸡块君的高中回忆

题意:炸鸡块君和同学们一共n个人去学校附近玩耍,但回学校时他们发现只有m个人带了校园卡,于是他们想到了这样一个策略:先让m个人带校园卡进入学校,再派一个人带着所有m张校园卡出来,重复上述过程,直到所有人进入学校。
题解:先考虑特殊情况,当m=1时除非n也为1否则无解,当m=n时一次就可以全部进去。一般的,让最后一次进m个人,剩下的n-m个人每次最多进m-1个,求最小的次数。
代码:
#include <iostream>
#include <cmath>
using namespace std;
typedef long long LL;
const int N = 1010;
int main() {
	int t;
	scanf("%d", &t);
	while (t -- ) {
		int n, m;
		scanf("%d%d", &n, &m);
		if (m == 1 && n > 1) {
			printf("-1\n");
		} else {
			if (m == n) {
				printf("1\n");
			} else {
				int res = 1;
                n -= m;
                res += n / (m - 1) * 2;
                if (n % (m - 1) != 0) {
                    res += 2 ;
                }
				printf("%d\n", res);
			}
		}
	}
	return 0;
} 

F. 中位数切分

题意:给定一个长为n的数组aa和一个整数m,你需要将其切成连续的若干段,使得每一段的中位数都大于等于m,求最多可以划分成多少段。
题解:先排序,在找见第一个大于等于m的位置,找一个最小的k使得排序后前k个数的中位数大于等于m,并使这k个数为一段,剩下的数各自为一段。
代码:
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
int a[N];
int n, m;
int find() {
	int l = 0, r = n - 1;
	while (l < r) {
		int mid = l + r >> 1;
		if (a[mid] >= m) {
			r = mid;
		} else {
			l = mid + 1;
		}
	}
	if (a[l] < m) {
		return n;
	} else {
		return l;
	}
}
int main() {
	int t;
	scanf("%d", &t);
	while (t -- ) {
		scanf("%d%d", &n, &m);
		for (int i = 0; i < n; i ++ ) {
			scanf("%d", &a[i]);
		}
		sort(a, a + n);
		int k = find();
		if (n % 2 == 0) {
			if (k > n / 2 - 1) {
				puts("-1");
			} else {
				printf("%d\n", n - 2 * k);
			}
		} else {
			if (k > n / 2) {
				puts("-1");
			} else {
				printf("%d\n", n - 2 * k);
			}
		}
		
	}
	return 0;
} 

H. 牛牛看云

题意:给定n个数求: ∑ i = 1 n ∑ j = i n ∣ a i + a j − 1000 ∣ \sum_{i=1}^{n}\sum_{j=i}^{n}|a_i+a_j-1000| i=1nj=inai+aj1000
题解:去绝对值+总结公式,先将n个数排序,从前往后遍历 i i i每次二分找见 a i + a j > = 1000 a_i+a_j>=1000 ai+aj>=1000最小的 j j j, j j j之前的数去绝对值后取反,j之后的数去绝对值为本身。
代码:
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int a[N], s[N];
int find(int k) {
	int l = 1, r = k - 1;
	int cp = 1000 - a[k];
	while (l < r) {
		int mid = l + r >> 1;
		if (a[mid] >= cp) {
			r = mid;
		} else {
			l = mid + 1;
		}
	}
	if (a[l] < cp) {
		return k;
	} else {
		return l;
	}
}
int main() {
	int n;
	scanf("%d", &n);
	LL res = 0;
	for (int i = 1; i <= n; i ++ ) {
		scanf("%d", &a[i]);
		res += abs(2 * a[i] - 1000);
	}
	sort(a + 1, a + n + 1);
	for (int i = 1; i <= n; i ++ ) {
		s[i] = s[i - 1] + a[i];
	}
	for (int i = 2; i <= n; i ++ ) {
		int k = find(i);
		if (k == i) {
			res += (i - 1) * (1000 - a[i]) - s[i - 1];
		} else {
			res += (i - 1 - k) * (a[i] - 1000) + s[i - 1] - s[k - 1];
			res += (k - 2) * (1000 - a[i]) - s[k - 1];
		}
	}
	printf("%lld\n", res);
	return 0;
} 

I. B站与各唱各的

题意:n个人唱m句话,每个人都可以唱这m句话中任意句话,若某句话所有人都唱了或都没唱,则这句话算唱失败,否则算唱成功,每个人互相看不到彼此,每个人都想着让最后唱成功的句子数尽可能的多,求最后唱成功的句子数。
题解:若每一个人都唱"最优方案",反而最后一句话都唱不成,所以最优就应该是每个人都随机唱,对于每一句话每个人有 1 2 \frac{1}{2} 21的概率去唱, 1 2 \frac{1}{2} 21的概率不去唱,因此对于一句话来说唱失败的概率为: ( 1 2 ) n + ( 1 2 ) n (\frac{1}{2})^n+(\frac{1}{2})^n (21)n+(21)n,唱成功的概率为: 1 − ( 1 2 ) n − ( 1 2 ) 1-(\frac{1}{2})^n-(\frac{1}{2}) 1(21)n(21),对于m句话最后在乘m,最后的公式就是: m 2 n − 2 2 n m\frac{2^n-2}{2^n} m2n2n2,最后在求个逆元就好。
代码:
#include <iostream>
using namespace std;
typedef long long LL;
const int mod = 1e9 + 7;

LL cal(LL a, int b) {
	LL res = 1;
	while (b) {
		if (b & 1) {
			res = res * a % mod;
		}
		a = a * a % mod;
		b >>= 1;
	}
	return res;
}
int main() {
	int t;
	scanf("%d", &t);
	while (t -- ) {
		int n, m;
		scanf("%d%d", &n, &m);
		printf("%lld\n", (cal(2, n) - 2 + mod) % mod * cal(cal(2, n), mod - 2) % mod * m % mod);
	}
	return 0;
} 

J. 小朋友做游戏

题意:a个安静的小盆友,b个吵闹的小盆友,每个小盆友对应一个幸福度,找n个小盆友围成一圈,吵闹的小盆友不能挨着,求符合题意的最大幸福度,无法找到则输出-1。
题解:若找的n个小盆友中必须有超过一半以上的吵闹的小盆友,则无论如何无法符合题意。否则,先对各个小盆友信服度从大到小排序,按要求找就行,特殊情况特殊考虑。
代码:
#include <iostream>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long LL;
const int N = 1e4 + 10;
int a[N], b[N];

bool cmp(int a, int b) {
	return a > b;
}
int main() {
	int t;
	scanf("%d", &t);
	while (t -- ) {
		int A, B, n;
		scanf("%d%d%d", &A, &B, &n);
		for (int i = 0; i < A; i ++ ) {
			scanf("%d", &a[i]);
		}
		for (int j = 0; j < B; j ++ ) {
			scanf("%d", &b[j]);
		}
		if (n - A > A) {
			printf("-1\n");
		} else {
			sort(a, a + A, cmp);
			sort(b, b + B, cmp);
			LL res = 0;
			int cnt = 0;
			int i = 0, j = 0;
			for (int k = 0; k < n; k ++ ) {
				if (j == B) {
					res += a[i ++ ];
				} else if (i == A) {
					res += b[j ++ ];
				} else {
					if (b[j] > a[i] && cnt < n / 2) {
						cnt ++ ;
						res += b[j ++ ];
					} else {
						res += a[i ++ ];
					}
				}
			}
			printf("%lld\n", res);
		}
	}
	return 0;
} 

L. 牛牛学走路

题意:从一个起点上下左右的走,求走的过程中距离起点的最大值。
题解:签到题,遍历就行。
代码:
#include <iostream>
#include <cmath>
using namespace std;
const int N = 1010;
char s[N];
int main() {
	int t;
	scanf("%d", &t);
	while (t -- ) {
		int n;
		scanf("%d%s", &n, s);
		int x = 0, y = 0;
		double res = 0;
		for (int i = 0; i < n; i ++ ) {
			if (s[i] == 'U') {
				y ++ ;
			} else if (s[i] == 'D') {
				y -- ;
			} else if (s[i] == 'L') {
				x -- ;
			} else {
				x ++ ;
			}
			res = max(res, sqrt(x * x + y * y));
		}
		printf("%.12lf\n", res);
	}
	return 0;
} 
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值