A.Neq Number(数位 dp+二分)
题意:
正整数 XXX 如果满足以下条件,则称NeqNeqNeq 数:
- 当 XXX 用十进制符号书写时,没有两个相邻的字符是相同的。
例如, 111 、 173173173 和 909090909090 是 NeqNeqNeq 数,而 222222 和 633563356335 不是。
给你一个正整数 KKK 。求 第KKK小的 NeqNeqNeq数。
分析:
考虑计算小于等于kkk的NeqNeqNeq数有多少个,利用二分枚举midmidmid,用数位dpdpdp计算小于等于midmidmid的NeqNeqNeq数有多少个,判断条件为相邻两位不同。
代码:
#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(数学)
题意:
在一个二维平面上有 NNN 个不同的点。第 iii 个点的坐标是 (xi,yi)(x_i,y_i)(xi,yi) 。
以这些点为顶点创建尽可能多的(非退化)三角形。在这里,同一个点不能作为多个三角形的顶点。求最多可创建的三角形个数。
非退化三角形是指三个顶点不相交的三角形。
分析:
设lll为平面上穿过点数最多的直线, 其上点数为mmm:
- m≥n−⌊n3⌋+1m \ge n - \lfloor \frac{n}{3} \rfloor +1m≥n−⌊3n⌋+1,答案为n−mn-mn−m,每从lll外取一个点,再从lll上选择两个点构成一个三角形。
- m≤n−⌊n3⌋m \le n - \lfloor \frac{n}{3}\rfloorm≤n−⌊3n⌋,答案可以取到上界⌊n3⌋\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(思维)
题意:
给你一个从 111 到 nnn 的整数排列 P=(P1,P2,…,PN)P=(P_1,P_2,\dots,P_N)P=(P1,P2,…,PN) 。
对于每个 i=1,2,…,Ni=1,2,\dots,Ni=1,2,…,N ,输出满足以下所有条件的一对整数 (l,r)(l,r)(l,r) 的 r−l+1r-l+1r−l+1 的最小值。如果不存在这样的 (l,r)(l,r)(l,r) ,则输出-1
。
- 1≤l≤i≤r≤N1 \leq l \leq i \leq r \leq N1≤l≤i≤r≤N
- r−l+1r-l+1r−l+1 是奇数。
- PPP 的连续子序列 (Pl,Pl+1,…,Pr)(P_l,P_{l+1},\dots,P_r)(Pl,Pl+1,…,Pr) 的中位数不是 PiP_iPi 。
这里,长度为 LLL (奇数)的整数序列 AAA 的 中位数定义为将 AAA 按升序排序后得到的序列 A′A'A′ 的 L+12\frac{L+1}{2}2L+1 /之值。
分析:
我们假设ansians_iansi表示iii位置的答案,因为ppp是一个排列,同时区间长度为奇数,所以有且仅有一个位置是该区间的中位数。所以满足ansi>kans_i > kansi>k的iii的个数不超过⌈nk⌉\lceil \frac{n}{k} \rceil⌈kn⌉。考虑从小到大枚举区间长度kkk,每次对还未确定答案的位置判断是否存在长度为 kkk的合法区间。用111表示大于pip_ipi的元素,−1-1−1表示小于pip_ipi的元素,000表示pip_ipi,为了使得pip_ipi成为中位数,区间内的和要为000。通过找规律发现,假定 iii 左右两侧都有足够多的元素,那么对任意奇数kkk仅有两种情况能够满足ansi>kans_i > kansi>k。所以每次判断只需要在前面的基础上判断常数种情况即可。上述规律不包括边界111和nnn,所以要对111和nnn单独进行处理。
代码:
#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 (图论)
题意:
给你一个有向图 GGG ,其中有 NNN 个顶点和 MMM 条边。顶点的编号从 111 到 NNN ,每条边都标有 (
或 )
。第 iii 条边是从顶点 uiu_iui 指向顶点 viv_ivi ,标签为 cic_ici 。该图不包含多条边或自循环。
在这个图中,对于任意两个顶点 sss 和 ttt ,都有一条从 sss 到 ttt 的路径。
请判断在图 GGG 中是否存在满足以下所有条件的路径:
- 路径的起点和终点顶点相同。
- 对于 i=1,2,…,Mi=1,2,\dots,Mi=1,2,…,M , iii 条 边在路径过程中至少使用了一次。
- 将路径中使用的边的标签按照使用顺序排列得到的字符串是一个正则括号序列。
正则括号序列是满足以下条件之一的字符串:
- 空字符串。
- 它是由
(
正则括号序列 AAA 和)
依次连接得到的字符串。 - 是由两个非空的正则括号序列 AAA 和 BBB 依次连接得到的字符串。
分析:
如果存在一条 (
与 )
出现次数相等的路径,那么必然可以通过更换路径起点来将形成的字符串循环移位为一个合法括号序列。因此答案路径是否存在等同于这样的路径是否存在。
有向图中的一条回路可以视作若干个环的组合。想要构造一条符合要求回路,我们可以先对于每条边求出一个包含它的环,再通过多次经过或增加某个环来平衡 (
与 )
的数量。
令 (
的出现次数严格大于 )
的出现次数的环为 AAA 类环,)
的出现次数严格大于 (
的出现次数的环为 BBB 类环。不难发现若 AAA,BBB 类环同时存在,一定可以通过调整两种环的经过次数构造出一组合法解。
如果只出现一种环,那么合法解一定不存在。通过反证法证明:
假设存在一组合法解,取一个 AAA 类环 tmptmptmp,由于合法解中每条边至少出现一次,我们将 tmptmptmp 中的边从合法解中删除(若某条边出现多次则只删除一次),合法解中剩下的部分一定由若干环组成,且由于合法解中 (
与 )
出现次数相等,去掉 tmptmptmp 后 )
的出现次数必然多于 (
。结合其由若干环组成,推断出这些环中至少存在一个 BBB 类环,与前提矛盾。
而如果两种环均未出现,可以任意构造合法解。
综上,只需要分别求出途中是否存在AAA 和BBB类环。令(
边的边权为−1−1−1,)
边的边权为111,通过bellman−fordbellman-fordbellman−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,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。