比赛题解: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都没看,亏死了