Codeforces Round 811
写在前面
比赛地址:https://codeforces.com/contest/1714。
没什么整理价值的题,但是 markdown 语法及博客文风复健。
A
\(t\) 组数据,每组数据给定一个睡觉时间,给定 \(n\) 个任务时间。时间均以 24 小时制的小时+分钟形式给出。
求到第一个任务时间的睡眠时间。
\(1\le t\le 100\),\(1\le n\le 10\)。
2S,256MB。
水。
//By:Luckyblock
/*
*/
#include <cstdio>
#include <cctype>
#include <algorithm>
//=============================================================
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = - 1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + ch - '0';
return f * w;
}
//=============================================================
int main() {
int t = read();
while (t --) {
int n = read(), H = read(), M = read();
int Tim = 60 * H + M, ans = 10000;
for (int i = 1; i <= n; ++ i) {
int h = read(), m = read();
int tim = 60 * h + m;
if (tim >= Tim) ans = std::min(ans, tim - Tim);
if (tim < Tim) ans = std::min(ans, 60 * 24 - Tim + tim);
}
printf("%d %d\n", ans / 60, ans % 60);
}
return 0;
}
B
\(t\) 组数据,每组数据给定一长度为 \(n\) 的数列 \(a\)。要求删去 \(a\) 的某个前缀,使 \(a\) 中不存在重复的数。
求删去的前缀最短的长度。
\(1\le t\le 10^4\),\(1\le n\le 2 \times 10^5\),\(1\le a_i\le n\),\(\sum n\le 2\times 10^5\)。
2S,256MB。
随便做。
\(a\) 的值域很小,开个桶倒序枚举 \(a_i\) 直至第一个重复元素即可。
//By:Luckyblock
/*
*/
#include <cstdio>
#include <cctype>
#include <algorithm>
const int kN = 2e5 + 10;
//=============================================================
int a[kN];
bool val[kN];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = - 1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + ch - '0';
return f * w;
}
//=============================================================
int main() {
int t = read();
while (t --) {
int n = read(), ans = 0;
for (int i = 1; i <= n; ++ i) a[i] = read();
for (int i = n; i >= 1; -- i) {
if (val[a[i]]) {
ans = i;
break;
}
val[a[i]] = true;
}
printf("%d\n", ans);
for (int i = n; i >= ans; -- i) val[a[i]] = false;
}
return 0;
}
C
\(t\) 组数据,每组数据给定整数 \(s\),求最小的整数 \(x\),使得 \(x\) 的各位之和为 \(s\)。
\(1\le t\le 45\),\(1\le s\le 45\)。
1S,256MB。
爆搜,打表。
//By:Luckyblock
/*
*/
#include <cstdio>
#include <cctype>
#include <algorithm>
//=============================================================
int ans[50] = {0,1,2,3,4,5,6,7,8,9,19,29,39,49,59,69,79,89,189,289,389,489,589,689,789,1789,2789,3789,4789,5789,6789,16789,26789,36789,46789,56789,156789,256789,356789,456789,1456789,2456789,3456789,13456789,23456789,123456789};
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = - 1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + ch - '0';
return f * w;
}
//=============================================================
int main() {
int t = read();
while (t --) {
int n = read();
printf("%d\n", ans[n]);
}
return 0;
}
D
\(q\) 组数据,每组数据给定字符串 \(t\) 和 \(n\) 个字符串 \(s_1\sim s_n\)。
当 \(s_i\) 为 \(t\) 的一个子串时,称 \(s_i\) 可以覆盖 \(t\) 该子串所在的区间。\(t\) 中的每个位置可以被多次覆盖,每个 \(s_i\) 可以被用来多次覆盖 \(t\)。
求使用所有 \(s_i\) 全部覆盖 \(t\) 所需的最少次数,并输出任意一种覆盖方案。
\(1\le q\le 100\),\(1\le |t|\le 100\),\(1\le n\le 10\),\(1\le |s|\le 10\)。
2S,256MB。
暴力,贪心。
数据范围这么小 KMP 都用不上。先暴力求子串,将子串抽象为若干线段,问题转化为给定一些数轴上的线段,求覆盖数轴最少的线段数量。
贪心地覆盖即可。将所有线段按照左端点升序,右端点降序排序,每次选出最长的、能够覆盖仍未被覆盖的位置的线段并使用即可。
//By:Luckyblock
/*
1
bababa
2
ba
aba
*/
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
const int kN = 2e4 + 10;
//=============================================================
int num, lent, lens, ans1, ans2[kN][2];
char t[kN], s[kN];
struct str {
int w, l, r;
} a[kN];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = - 1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + ch - '0';
return f * w;
}
bool CMP(str fir, str sec) {
if (fir.l == sec.l) return fir.r > sec.r;
return fir.l < sec.l;
}
void Work(int k_) {
lens = strlen(s + 1);
for (int j = 1; j + lens - 1 <= lent; ++ j) {
bool flag = true;
for (int k = j, l = 1; l <= lens; ++ k, ++ l) {
if (t[k] != s[l]) {
flag = false;
break;
}
}
if (flag) a[++ num] = (str) {k_, j, j + lens - 1};
}
}
//=============================================================
int main() {
int q = read();
while (q --) {
scanf("%s", t + 1);
lent = strlen(t + 1);
num = ans1 = 0;
int n = read(), end = 1;
for (int i = 1; i <= n; ++ i) {
scanf("%s", s + 1);
Work(i);
}
std::sort(a + 1, a + num + 1, CMP);
for (int i = 1; i <= num && end <= lent; ) {
int far = 0, p = 0;
for (; i <= num && a[i].l <= end; ++ i) {
if (a[i].r > far) far = a[i].r, p = i;
}
if (far <= end - 1) break;
end = far + 1, ++ ans1;
ans2[ans1][0] = a[p].w, ans2[ans1][1] = a[p].l;
}
if (end > lent) {
printf("%d\n", ans1);
for (int i = 1; i <= ans1; ++ i) printf("%d %d\n", ans2[i][0], ans2[i][1]);
} else {
printf("-1\n");
}
}
return 0;
}
E
\(t\) 组数据,每组数据给定一长度为 \(n\) 的数列 \(a\),给定一种操作。每次操作可以选定任意数列元素 \(a_i\),使 \(a_i+(a_i\bmod 10)\)。
求对数列进行任意次操作后,数列中各元素能否相等。
\(1\le t\le 10^4\),\(1\le n\le 2\times 10^5\),\(0\le a_i\le 10^9\),\(\sum n\le 2\times 10^5\)。
2S,256MB。
结论。
手玩一下:
可以发现奇数进行操作后将变成偶数,偶数的操作以 4 次为一个周期,一个周期后该数将 \(+20\),特别的,个位数为 0 的数操作后不变,个位数为 5 的数进行一次操作后末尾将变成 0。
如果操作后所有数能够相等,则任意两个数经操作后可以相等,先考虑两个数的情况。如果两个数本就相等则跳过,再将两个数中的奇数一次操作后变为偶数,再经过若干次操作使两个数个位相等。可以发现,此时两数可以相等的充要条件是两数之差为 20 的倍数。
上述算法拓展至整个数列即可。将预先处理后的数列升序排序,从小到大处理即可。
注意特判个位为 0 的数。
//By:Luckyblock
/*
12 2
14 4
18 8
26 6
32 2
34 4
38 8
46 6
52 2
44 4
48 8
56 6
62 2
*/
#include <cstdio>
#include <cctype>
#include <algorithm>
const int kN = 2e5 + 10;
//=============================================================
int a[kN];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = - 1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + ch - '0';
return f * w;
}
//=============================================================
int main() {
int t = read();
while (t --) {
int n = read(), flag = true;
for (int i = 1; i <= n; ++ i) a[i] = read();
for (int i = 1; i <= n; ++ i) {
if (a[i] % 2 == 1) a[i] += (a[i] % 10);
while (a[i] % 10 != 2 && a[i] % 10 != 0) a[i] += (a[i] % 10);
}
std::sort(a + 1, a + n + 1);
if (a[1] == a[n]) {
printf("Yes\n");
continue;
}
for (int i = 2; i <= n; ++ i) {
if (a[i] == a[i - 1]) continue;
if (((a[i] - a[i - 1]) % 20) || (a[i - 1] % 10 == 0)) {
flag = false;
break;
}
}
printf("%s\n", flag ? "Yes" : "No");
}
return 0;
}
F
\(t\) 组数据,每组数据给定参数 \(n\)、\(d_{12}\)、\(d_{23}\)、\(d_{31}\),分别代表树的节点个数、节点 \(1\) 到 \(2\)、\(2\) 到 \(3\)、\(3\) 到 \(1\) 的距离,要求构造一棵树,满足上述要求。
求是否存在对应的构造方案,若存在则输出方案。
\(1\le t\le 10^4\),\(3\le n\le 2\times 10^5\),\(1\le d_{12},d_{23},d_{31}\le n - 1\)。
2S,256MB。
思路简单但是细节麻烦的构造。
首先构造出 \(1\) 到 \(2\) 的链,考虑 \(3\) 与该链的相对位置:
- 在 \(1\) 到 \(2\) 的链上某点上。
- 在 \(1\) 到 \(2\) 的链上某点向外引出的一条链上。
- 在 \(1\) 到 \(2\) 的链的延长线上。
发现仅有以上三种合法情况存在,则可根据上述情况的距离关系判断是否存在方案,然后大力讨论构造即可。具体参考代码。
//By:Luckyblock
/*
1
5 4 2 2
1
5 3 3 2
1
5 3 1 4
1
5 3 4 1
1
5 1 3 4
1
5 2 3 3
1
5 3 2 3
1
5 4 1 1
*/
#include <cstdio>
#include <cctype>
#include <algorithm>
const int kN = 2e5 + 10;
//=============================================================
int n, d12, d23, d31, num;
int edgenum, ans[kN][2];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = - 1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + ch - '0';
return f * w;
}
int abs(int x) {
return x >= 0 ? x : -x;
}
bool Judge() {
if (abs(d23 - d31) > d12) return false;
if ((abs(d23 - d31) % 2) != (d12 % 2)) return false;
if (abs(d23 - d31) == d12) {
if (d23 > d31) return (n >= d23);
else return (n >= d31);
} else {
if (d23 + d31 < d12) return false;
return (n >= ((d12 + d31 + d23) / 2 + 1));
}
}
void Work() {
num = 3, edgenum = 0;
if (d12 == 1) {
ans[++ edgenum][0] = 1, ans[edgenum][1] = 2;
} else {
ans[++ edgenum][0] = 1, ans[edgenum][1] = ++ num;
for (int i = 2; i < d12; ++ i) {
ans[++ edgenum][0] = num, ans[edgenum][1] = ++ num;
}
ans[++ edgenum][0] = num, ans[edgenum][1] = 2;
}
if (abs(d23 - d31) == d12) {
if (d23 > d31) {
if (d31 > 1) {
ans[++ edgenum][0] = 1, ans[edgenum][1] = ++ num;
for (int i = d12 + 1; i < d23 - 1; ++ i) {
ans[++ edgenum][0] = num;
ans[edgenum][1] = ++ num;
}
ans[++ edgenum][0] = num, ans[edgenum][1] = 3;
} else {
ans[++ edgenum][0] = 1, ans[edgenum][1] = 3;
}
for (int i = num + 1; i <= n; ++ i) {
ans[++ edgenum][0] = i, ans[edgenum][1] = 1;
}
} else {
if (d23 > 1) {
ans[++ edgenum][0] = 2, ans[edgenum][1] = ++ num;
for (int i = d12 + 1; i < d31 - 1; ++ i) {
ans[++ edgenum][0] = num;
ans[edgenum][1] = ++ num;
}
ans[++ edgenum][0] = num, ans[edgenum][1] = 3;
} else {
ans[++ edgenum][0] = 2, ans[edgenum][1] = 3;
}
for (int i = num + 1; i <= n; ++ i) {
ans[++ edgenum][0] = i, ans[edgenum][1] = 1;
}
}
} else {
int pos;
if (d23 > d31) {
pos = 4;
for (int i = d12 - 2; i != abs(d23 - d31); i -= 2) ++ pos;
} else {
pos = num;
for (int i = d12 - 2; i != abs(d23 - d31); i -= 2) -- pos;
}
if (d31 == pos - 2) {
ans[++ edgenum][0] = pos, ans[edgenum][1] = 3;
} else if (d31 == pos - 3) {
ans[pos - 3][1] = 3, ans[pos - 2][0] = 3;
for (int i = 1; i <= edgenum; ++ i) {
if (ans[i][0] > pos) ans[i][0] --;
if (ans[i][1] > pos) ans[i][1] --;
}
-- num;
} else {
ans[++ edgenum][0] = pos, ans[edgenum][1] = ++ num;
for (int i = pos - 2; i < d31 - 1; ++ i) {
ans[++ edgenum][0] = num;
ans[edgenum][1] = ++ num;
}
ans[++ edgenum][0] = num, ans[edgenum][1] = 3;
}
for (int i = num + 1; i <= n; ++ i) {
ans[++ edgenum][0] = i, ans[edgenum][1] = 1;
}
}
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
// freopen("2.txt", "w", stdout);
int t = read();
for (int i = 1; i <= t; ++ i) {
n = read(), d12 = read(), d23 = read(), d31 = read();
// printf("%d:", i);
if (!Judge()) {
printf("NO\n");
continue;
}
printf("YES\n");
Work();
for (int i = 1; i < n; ++ i) printf("%d %d\n", ans[i][0], ans[i][1]);
}
return 0;
}
G
\(t\) 组数据,每组数据给定一棵 \(n\) 个节点的有根树,根节点为 \(1\)。第 \(i\) 条边有两个边权 \(a_i,b_i\)。定义一段路径的前缀为:从起点出发的,方向与该路径相同且长度不大于该路径的路径。
对于节点 \(2\sim n\),记从根节点到该节点的路径上边权 \(a_i\) 之和为 \(x\),求使得边权 \(b_i\) 之和不大于 \(x\) 的最长的路径前缀的长度。
\(1\le t\le 10^4\),\(2\le n\le 2\times 10^5\),\(1\le a_i,b_i\le 10^9\),\(\sum n \le 2\times 10^5\)。
3S,256MB。
dfs + 二分搜索。
dfs 过程中记录从根到该节点的路径上 \(b_i\) 的前缀和,二分即可。
//By:Luckyblock
/*
*/
#include <cstdio>
#include <cctype>
#include <algorithm>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, ans[kN];
int edgenum, v[kN], ne[kN], a[kN], b[kN], head[kN];
LL sumb[kN];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = - 1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + ch - '0';
return f * w;
}
void Add(int u_, int v_, int a_, int b_) {
v[++ edgenum] = v_;
a[edgenum] = a_;
b[edgenum] = b_;
ne[edgenum] = head[u_];
head[u_] = edgenum;
}
int Query(int dep_, LL suma_) {
int l = 1, r = dep_ - 1, ret = 0;
for (; l <= r; ) {
int mid = l + r >> 1;
if (sumb[mid] <= suma_) {
ret = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
return ret;
}
void Dfs(int u_, int dep_, LL suma_) {
ans[u_] = Query(dep_, suma_);
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i], a_ = a[i], b_ = b[i];
sumb[dep_] = sumb[dep_ - 1] + 1ll * b_;
Dfs(v_, dep_ + 1, suma_ + 1ll * a_);
}
}
void Init() {
edgenum = 0;
for (int i = 1; i < n; ++ i) ans[i] = head[i] = ne[i] = 0;
}
//=============================================================
int main() {
int t = read();
while (t --) {
Init();
n = read();
for (int i = 2; i <= n; ++ i) {
int u = read(), x = read(), y = read();
Add(u, i, x, y);
}
Dfs(1, 1, 0);
for (int i = 2; i <= n; ++ i) printf("%d ", ans[i]);
printf("\n");
}
return 0;
}
写在最后
AK 一场傻逼 div3,耗时 5h+。
哈哈。
马达马达。