比赛题解:Codeforces 690div3

比赛题解:Codeforces 690div3

 A :题目大意:按顺序输出序列

模拟即可

B:题目大意:可以删除中间连续的一个子串,问能否通过一次以内的操作使得串变为2020

开头结尾拼成2020,枚举开头和结尾状态判断即可

C:题目大意:要找一个最小的正整数,使得不出现重复数字,和为给定值

123456789的和为45,超过45的数都不能被凑出来

对于小于等于45的可以贪心的取末尾最大,这样高位就会更小,得到一个最小的数

D:题目大意:每次可以将序列中的一个元素删除,并将该处的值加到它两边中的其中一个,问最少多少次能把序列中所有元素变得相同

3000的数据范围可以考虑n ^ 2的做法

首先可以考虑,删除之后总和不变,对于整个序列,可以枚举分成了多少段,这样也就知道了每段的和

然后考虑如何操作

删除操作过后只能将一段连续的值加到一个位置,那么可以枚举右端点的时候,保存上一次的端点,通过前缀和的形式来判断枚举的每一段是否合法,取最小值即可

E:题目大意:有n个数值域1-n,从其中选m个数,最大值和最小值的差不超过k,问有多少种方法。n = 2e5, m = 100。

最开始的想法是值域k之内的数拿出来排列组合,但是这样会有重复的情况,考虑如何消除重复的情况。

可以考虑容斥,枚举最小值,考虑每次最小值为i的方案数

拿出i到i + k之中的所有数做排列组合,然后减去i + 1到i + k中的答案,为i为最小值时的答案,累加所有答案即可

时间复杂度O(n),预处理阶乘之后可以O(1)得到组合数,所有的数可以前缀和优化

#include<iostream>
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define db double
using namespace std;

ll read() {
	ll rt = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-') f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <='9') {
		rt = (rt << 3) + (rt << 1) + ch - '0';
		ch = getchar();
	}
	return rt * f;
}
const int Mod = 1e9 + 7;
const int maxn = 2e5 + 5;
ll T, n, m, k;
ll a[maxn], cnt[maxn], mi[maxn], inv[maxn], sum[maxn];

ll mpow(ll a, ll b) {
	ll rt = 1;
	while (b) {
		if (b & 1) rt = rt * a % Mod;
		b >>= 1;
		a = a * a % Mod;
	}
	return rt;
}

ll solve(ll lf, ll rg) {
	ll N = sum[rg] - sum[lf - 1];
	if (N < m) return 0;
	return mi[N] * inv[m] % Mod * inv[N - m] % Mod;
}

int main() {
	T = read();
	mi[0] = 1;
	inv[0] = 1;
	for (ll i = 1; i <= 200000; i++) mi[i] = mi[i - 1] * i % Mod, inv[i] = mpow(mi[i], Mod - 2);
	while (T--) {
		n = read(), m = read(), k = read();
		for (int i = 1; i <= n; i++) sum[i] = 0, cnt[i] = 0;
		for (int i = 1; i <= n; i++) {
			a[i] = read();
			cnt[a[i]]++;
		}
		for (int i = 1; i <= n; i++) {
			sum[i] = sum[i - 1] + cnt[i];
		}
		ll ans = 0;
		for (ll i = 1; i <= n; i++) {
			ans += solve(i, min(i + k, n));
			ans -= solve(i + 1, min(i + k, n));
			ans = (ans % Mod + Mod) % Mod;
		}
		printf("%lld\n", ans);
	}
	return 0;
}
 

F:题目大意:有很多个区间L-R,问最少需要删除多少个区间,使得其中一个区间和其他所有区间相交。

感觉F比E简单。。枚举区间,和这个区间不相交的区间满足条件: 右端点在该区间左端点左边或者左端点在该区间右端点右边,可以离散化之后前缀后缀求一下和,然后枚举求最小值即可。

复杂度nlogn(离散化复杂度)

#include<iostream>
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define db double
using namespace std;

int read() {
	int rt = 0, f = 1;
	char ch = getchar();
	while (ch < '0' || ch > '9') {
		if (ch == '-') f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <='9') {
		rt = (rt << 3) + (rt << 1) + ch - '0';
		ch = getchar();
	}
	return rt * f;
}

const int maxn = 2e5 + 5;

int T, n;

struct Node {
	int l, r;
}p[maxn]; 
int disc[maxn << 1], cnt, sumL[maxn << 1], sumR[maxn << 1];

int main() {
	T = read();
	while (T--) {
		for (int i = 1; i <= cnt; i++) sumL[i] = sumR[i] = 0;
		n = read();
		for (int i = 1; i <= n; i++) p[i].l = read(), p[i].r = read(), disc[2 * i - 1] = p[i].l, disc[2 * i] = p[i].r;
		sort(disc + 1, disc + 1 + 2 * n);
		cnt = unique(disc + 1, disc + 1 + 2 * n) - disc - 1;
		for (int i = 1; i <= n; i++) {
			p[i].l = lower_bound(disc + 1, disc + 1 + cnt, p[i].l) - disc;
			p[i].r = lower_bound(disc + 1, disc + 1 + cnt, p[i].r) - disc;
		}		
		for (int i = 1; i <= n; i++) {
			sumL[p[i].r]++;
			sumR[p[i].l]++;
		}
		for (int i = 1; i <= cnt; i++) {
			sumL[i] += sumL[i - 1];
		}
		for (int i = cnt; i >= 1; i--) {
			sumR[i] += sumR[i + 1];
		}
		int ans = 1e9;
		for (int i = 1; i <= n; i++) {
			ans = min(ans, sumL[p[i].l - 1] + sumR[p[i].r + 1]);
		}
		printf("%d\n", ans);
	}
	return 0;
}
/*
4
1 
3
1 4
2 3
3 6
1 
4
1 2
2 3
3 5
4 5
1 
5
1 2
3 8
4 5
6 7
9 10
5
1 5
2 4
3 5
3 8
4 8
*/ 
 

比赛的时候没写完E hard,连F都没看,亏死了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值