A.Neq Number(数位 dp+二分)
题意:
正整数 X X X 如果满足以下条件,则称 N e q Neq Neq 数:
- 当 X X X 用十进制符号书写时,没有两个相邻的字符是相同的。
例如, 1 1 1 、 173 173 173 和 9090 9090 9090 是 N e q Neq Neq 数,而 22 22 22 和 6335 6335 6335 不是。
给你一个正整数 K K K 。求 第 K K K小的 N e q Neq Neq数。
分析:
考虑计算小于等于 k k k的 N e q Neq Neq数有多少个,利用二分枚举 m i d mid mid,用数位 d p dp dp计算小于等于 m i d mid mid的 N e q Neq Neq数有多少个,判断条件为相邻两位不同。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL dp[20][15], a[15];
LL dfs(int pos, int pre, bool limit, bool flag) {
if (pos == 0)
return 1;
if (!limit && !flag && dp[pos][pre] != -1)
return dp[pos][pre];
LL mx = 9, ans = 0;
if (limit)
mx = a[pos];
for (int i = 0; i <= mx; i++) {
if (!flag && i == pre)
continue;
ans += dfs(pos - 1, i, limit & (i == mx), flag && (i == 0));
}
if (!limit && !flag)
dp[pos][pre] = ans;
return ans;
}
LL get(LL x) {
LL tot = 0;
while (x)
a[++tot] = x % 10, x /= 10;
return dfs(tot, 0, 1, 1);
}
int main() {
memset(dp, -1, sizeof(dp));
LL t;
cin >> t;
while (t--) {
LL x;
cin >> x;
LL l = 0, r = 1e18, mid, ans;
while (l <= r) {
mid = (l + r) / 2;
if (get(mid) <= x)
ans = mid, l = mid + 1;
else
r = mid - 1;
}
cout << ans + 1 << endl;
}
return 0;
}
B.Make Many Triangles(数学)
题意:
在一个二维平面上有
N
N
N 个不同的点。第
i
i
i 个点的坐标是
(
x
i
,
y
i
)
(x_i,y_i)
(xi,yi) 。
以这些点为顶点创建尽可能多的(非退化)三角形。在这里,同一个点不能作为多个三角形的顶点。求最多可创建的三角形个数。
非退化三角形是指三个顶点不相交的三角形。
分析:
设 l l l为平面上穿过点数最多的直线, 其上点数为 m m m:
- m ≥ n − ⌊ n 3 ⌋ + 1 m \ge n - \lfloor \frac{n}{3} \rfloor +1 m≥n−⌊3n⌋+1,答案为 n − m n-m n−m,每从 l l l外取一个点,再从 l l l上选择两个点构成一个三角形。
- m ≤ n − ⌊ n 3 ⌋ m \le n - \lfloor \frac{n}{3}\rfloor m≤n−⌊3n⌋,答案可以取到上界 ⌊ n 3 ⌋ \lfloor \frac{n}{3} \rfloor ⌊3n⌋
求出平面上穿过点数最多的直线上点的个数,二者取小即可。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL n, tmp, x[305], y[305];
bool check(LL ax, LL ay, LL bx, LL by, LL cx, LL cy) {
return (bx - ax) * (cy - by) == (by - ay) * (cx - bx);
}
int main() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> x[i] >> y[i];
for (int i = 1; i < n; i++) {
for (int j = i + 1; j <= n; j++) {
LL cnt = 2;
for (int k = 1; k <= n; k++) {
if (k == i || k == j)
continue;
if (check(x[i], y[i], x[j], y[j], x[k], y[k]))
cnt++;
}
tmp = max(tmp, cnt);
}
}
cout << min(n / 3, n - tmp) << endl;
return 0;
}
C.Not Median(思维)
题意:
给你一个从 1 1 1 到 n n n 的整数排列 P = ( P 1 , P 2 , … , P N ) P=(P_1,P_2,\dots,P_N) P=(P1,P2,…,PN) 。
对于每个
i
=
1
,
2
,
…
,
N
i=1,2,\dots,N
i=1,2,…,N ,输出满足以下所有条件的一对整数
(
l
,
r
)
(l,r)
(l,r) 的
r
−
l
+
1
r-l+1
r−l+1 的最小值。如果不存在这样的
(
l
,
r
)
(l,r)
(l,r) ,则输出-1
。
- 1 ≤ l ≤ i ≤ r ≤ N 1 \leq l \leq i \leq r \leq N 1≤l≤i≤r≤N
- r − l + 1 r-l+1 r−l+1 是奇数。
- P P P 的连续子序列 ( P l , P l + 1 , … , P r ) (P_l,P_{l+1},\dots,P_r) (Pl,Pl+1,…,Pr) 的中位数不是 P i P_i Pi 。
这里,长度为 L L L (奇数)的整数序列 A A A 的 中位数定义为将 A A A 按升序排序后得到的序列 A ′ A' A′ 的 L + 1 2 \frac{L+1}{2} 2L+1 /之值。
分析:
我们假设 a n s i ans_i ansi表示 i i i位置的答案,因为 p p p是一个排列,同时区间长度为奇数,所以有且仅有一个位置是该区间的中位数。所以满足 a n s i > k ans_i > k ansi>k的 i i i的个数不超过 ⌈ n k ⌉ \lceil \frac{n}{k} \rceil ⌈kn⌉。考虑从小到大枚举区间长度 k k k,每次对还未确定答案的位置判断是否存在长度为 k k k的合法区间。用 1 1 1表示大于 p i p_i pi的元素, − 1 -1 −1表示小于 p i p_i pi的元素, 0 0 0表示 p i p_i pi,为了使得 p i p_i pi成为中位数,区间内的和要为 0 0 0。通过找规律发现,假定 i i i 左右两侧都有足够多的元素,那么对任意奇数 k k k仅有两种情况能够满足 a n s i > k ans_i > k ansi>k。所以每次判断只需要在前面的基础上判断常数种情况即可。上述规律不包括边界 1 1 1和 n n n,所以要对 1 1 1和 n n n单独进行处理。
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 5e5 + 5;
LL n, a[N], sum[N], i, k;
vector<LL> tmp1, tmp2;
int main() {
cin >> n;
for (i = 1; i <= n; i++)
cin >> a[i];
for (i = 1; i <= n; i++) {
if (i > 2 && ((a[i - 2] < a[i]) == (a[i - 1] < a[i])))
sum[i] = 3;
else if (i > 1 && i < n && ((a[i - 1] < a[i]) == (a[i + 1] < a[i])))
sum[i] = 3;
else if (i < n - 1 && ((a[i + 1] < a[i]) == (a[i + 2] < a[i])))
sum[i] = 3;
else
tmp2.push_back(i);
}
for (k = 5; k <= n; k += 2) {
tmp1 = tmp2;
tmp2.clear();
for (auto i: tmp1) {
if (i - k + 2 > 0 && i < n && (a[i - k + 2] < a[i]) != (a[i - 1] < a[i]))
sum[i] = k;
else if (i - k + 1 > 0 && (a[i - k + 1] < a[i]) == (a[i - k + 2] < a[i]))
sum[i] = k;
else if (i + k - 2 <= n && i > 1 && (a[i + k - 2] < a[i]) != (a[i + 1] < a[i]))
sum[i] = k;
else if (i + k - 1 <= n && (a[i + k - 1] < a[i]) == (a[i + k - 2] < a[i]))
sum[i] = k;
else
tmp2.push_back(i);
}
}
for (auto i: tmp2)
sum[i] = -1;
for (i = 1; i <= n; i++)
cout << sum[i] << ' ';
return 0;
}
D.Bracket Walk (图论)
题意:
给你一个有向图
G
G
G ,其中有
N
N
N 个顶点和
M
M
M 条边。顶点的编号从
1
1
1 到
N
N
N ,每条边都标有 (
或 )
。第
i
i
i 条边是从顶点
u
i
u_i
ui 指向顶点
v
i
v_i
vi ,标签为
c
i
c_i
ci 。该图不包含多条边或自循环。
在这个图中,对于任意两个顶点 s s s 和 t t t ,都有一条从 s s s 到 t t t 的路径。
请判断在图 G G G 中是否存在满足以下所有条件的路径:
- 路径的起点和终点顶点相同。
- 对于 i = 1 , 2 , … , M i=1,2,\dots,M i=1,2,…,M , i i i 条 边在路径过程中至少使用了一次。
- 将路径中使用的边的标签按照使用顺序排列得到的字符串是一个正则括号序列。
正则括号序列是满足以下条件之一的字符串:
- 空字符串。
- 它是由
(
正则括号序列 A A A 和)
依次连接得到的字符串。 - 是由两个非空的正则括号序列 A A A 和 B B B 依次连接得到的字符串。
分析:
如果存在一条 (
与 )
出现次数相等的路径,那么必然可以通过更换路径起点来将形成的字符串循环移位为一个合法括号序列。因此答案路径是否存在等同于这样的路径是否存在。
有向图中的一条回路可以视作若干个环的组合。想要构造一条符合要求回路,我们可以先对于每条边求出一个包含它的环,再通过多次经过或增加某个环来平衡 (
与 )
的数量。
令 (
的出现次数严格大于 )
的出现次数的环为
A
A
A 类环,)
的出现次数严格大于 (
的出现次数的环为
B
B
B 类环。不难发现若
A
A
A,
B
B
B 类环同时存在,一定可以通过调整两种环的经过次数构造出一组合法解。
如果只出现一种环,那么合法解一定不存在。通过反证法证明:
假设存在一组合法解,取一个
A
A
A 类环
t
m
p
tmp
tmp,由于合法解中每条边至少出现一次,我们将
t
m
p
tmp
tmp 中的边从合法解中删除(若某条边出现多次则只删除一次),合法解中剩下的部分一定由若干环组成,且由于合法解中 (
与 )
出现次数相等,去掉
t
m
p
tmp
tmp 后 )
的出现次数必然多于 (
。结合其由若干环组成,推断出这些环中至少存在一个
B
B
B 类环,与前提矛盾。
而如果两种环均未出现,可以任意构造合法解。
综上,只需要分别求出途中是否存在
A
A
A 和
B
B
B类环。令(
边的边权为
−
1
−1
−1,)
边的边权为
1
1
1,通过
b
e
l
l
m
a
n
−
f
o
r
d
bellman-ford
bellman−ford求负环即可。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 5010;
const int M = 2 * N;
struct Edge {
int x, y;
char c;
} edge[M];
int f[N], g[N], save1[N];
int n, m;
int solve(char c) {
memset(f, 0, sizeof f);
for (int t = 1; t <= n; t++) {
memcpy(g, f, sizeof g);
for (int j = 1; j <= m; j++)
g[edge[j].y] = min(g[edge[j].y], g[edge[j].x] + ((edge[j].c == c) ? -1 : 1));
swap(f, g);
}
for (int i = 1; i <= n; i++)
save1[i] = f[i];
for (int t = 1; t <= n; t++) {
memcpy(g, f, sizeof g);
for (int j = 1; j <= m; j++)
g[edge[j].y] = min(g[edge[j].y], g[edge[j].x] + ((edge[j].c == c) ? -1 : 1));
swap(f, g);
}
for (int i = 1; i <= n; i++)
if (save1[i] != f[i])
return true;
return false;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++)
cin >> edge[i].x >> edge[i].y >> edge[i].c;
int f1 = solve('('), f2 = solve(')');
if (f1 ^ f2)
cout << "No" << endl;
else
cout << "Yes" << endl;
return 0;
}
赛后交流
在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。
群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。