2023 11月 AtCoder 做题记录
AGC037F
第一步,考虑判断序列是否合法。
通过对于属于等级 的定义将定义反推: 中最小的元素 ,找到所有 的连续段。设一个连续段的长度是 ,若 则不合法,否则将这一段合并为 个 ,直到 中只剩下一种元素,同时为了满足这个定义,这个元素一定是原 最大值,且不再合并这个元素。
令 最大值为 ,显然,合法情况有两种:
- 1. 若 的数量为 ,这种情况当且仅当原序列的长度为 , 属于等级 ,合法。
- 2. 若 的数量不少于 , 属于等级 ,合法。
考虑将这个过程应用到个序列 中。
从小到大考虑 中出现过的值 ,当考虑到 时,计算 中所有最大值为 的子段的中合法的数量,用栈维护。
现在,转化为问题:有一个序列 ,其中 为一个所有元素都 的序列,求这个序列中有多少个子段满足其属于等级 。
设 为 中的最大值,对于一个序列 维护信息:
- 表示 中至多能合并出 个 的前缀个数。
- 表示 中至多能合并出 个 的后缀个数。
- 表示 至多能合并出的 的个数(由 求出)。
根据 ,可求出 , 中至多能合并出 个 的前缀与后缀个数, 至多能合并出的 的个数。
假设我们现在对于 中的所有 都求出了 ,则我们首先可以求出 中至多能合并出 个 的前/后缀个数,以及 至多能合并出的 的个数。之后就可以求出答案了。
时空复杂度
#include <bits/stdc++.h>
#define rint register int
#define int long long
#define endl '\n'
using namespace std;
const int N = 2e5 + 5;
int n, l;
int s[N], f[N], g[N];
int sf[N], sg[N];
int ans;
int top;
void pop()
{
int k = 1;
int x = s[top];
while (s[top - 1] == x) top--, k++;
top--;
if (k < l)
{
while (top != 0) pop();
return;
}
for (rint i = 1; i <= k; i++)
{
sf[i] = sf[i - 1] + f[i + top];
sg[i] = sg[i - 1] + g[i + top];
}
for (rint i = l; i <= k; i++)
{
ans += sf[i - l + 1] * g[i + top];
}
int m = k / l;
for (rint i = 1; i <= m; i++)
{
f[top + i] = sf[k - l * (m - i + 1) + 1] - sf[max(k - l * (m - i + 2) + 1, 0ll)];
g[top + i] = sg[min(l * (i + 1) - 1, k)] - sg[l * i - 1];
}
int p = 0;
for (rint i = l; i <= m; i++)
{
p += f[top + i - l + 1];
ans -= p * g[top + i];
}
for (rint i = 1; i <= m; i++)
{
s[++top] = x + 1;
}
}
signed main()
{
cin >> n >> l;
ans = n;
for (rint i = 1; i <= n; i++)
{
int a;
cin >> a;
while (top != 0 && s[top] < a) pop();
s[++top] = a;
f[top] = g[top] = 1;
}
while (top != 0) pop();
cout << ans << endl;
return 0;
}
ARC102F
题目传送门
题目大意为,给定长度为 的排列 , 可以进行无限次操作, 问最终能否将其排成升序. 其中, 一次操作定义为选择 使得 且 . 交换
在上一道题的求解中,用到了逆向思维,这道题同样如此。
对于此题,假设 择 使得 且 . 交换 中, 为合法点。如果 且 为合法点,显然,无论怎样操作都会自相矛盾对吧,发现, 若 为合法点, 则在所有操作之前,
通过这个性质我们可以先筛掉一些一定不合法的序列。
但是只通过这个性质并不全。
定义数组 bool a[i]=[i==p[i]]
, 如果 中有连续的 个 出现, 无解. 考虑将 分成若干个区间, 使得同一个区间内没有两个连续的数相等. 易证区间与区间之间不存在数的交换。个区间的值域的范围和下标的范围应该要一样. 在一个区间内, 考虑把 的数单独拿出来, 如果其最长下降子序列的长度大于等于三则无解. 考虑有 个数 , 显然它们是无法交换过来的.
此做法时空复杂度
#include <bits/stdc++.h>
#define rint register int
#define int long long
#define endl '\n'
using namespace std;
const int N = 5e5 + 5;
int n, p[N];
bool a[N];
int minn[N];
int s[N], top;
signed main()
{
cin >> n;
p[n + 1] = n + 1;
for (rint i = 1; i <= n; i++)
{
cin >> p[i];
}
for (rint i = 1; i <= n; i++)
{
if (p[i - 1] != i - 1 && p[i] != i && p[i + 1] != i + 1)
{
puts("No");
return 0;
}
else if (i == p[i])
{
a[i] = 1;
}
}
rint j = 0;
for (rint i = 1; i <= n; i = j + 1)
{
j = i;
while (j < n && a[j] != a[j + 1]) j++;
top = 0;
for (rint k = i; k <= j; k++)
{
if (p[k] < i || j < p[k])
{
puts("No");
return 0;
}
if (!a[k])
{
s[++top] = p[k];
}
}
minn[top] = s[top];
for (rint k = top - 1; k >= 1; k--)
{
minn[k] = min(minn[k + 1], s[k]);
}
int maxx = s[1];
for (rint k = 2; k < top; k++)
{
maxx = max(maxx, s[k]);
if (s[k] > minn[k] && s[k] < maxx)
{
puts("No");
return 0;
}
}
}
puts("Yes");
return 0;
}
AGC039D
傻逼数学题平面几何
结论:对于任意 ,三段弧中点的坐标之和,就是 内心的坐标。
由垂心角平分线定理和欧拉线很容易就能整出来。代码不粘了。
AGC021E
很好的一道组合数学题:
我们可以先手搓几个样例玩玩,通过总结,发现最多能喂 只变色龙的都是这样的序列:先是一段 ,然后一段一段 相连的东西(严格来说是 相连......)
然后最后一段先是 后是
如果最后喂给那只被钦定的变色龙的 时,最多只能弄出 只红变色龙,显然,手上的那个球只能是 。否则要拯救那只变色龙,手上的球只能是
通过 个 的位置确定整个序列
方案数是
ABC197F
回文字符串的特点是对称性,不难想到这个题是双向搜索
先考虑长度为偶数的情况,初始两个位置分别是 ,每次两个位置各选择一条连出的边,满足边上的字母相同,并向另一个端点移动,最终两个位置重合。
设这个相同的位置是 ,此时 的路径和 的路径所形成的字符串相同。将 反过来,再在前面接上 ,最终会得到一条满足条件的路径。把两个位置放进状态中,再 bfs。
奇数的情况,找到两个位置之间恰好隔了一条边的情况即可
AGC022F
题目传送门
一道不错的 ,但是实力不够,学了一下 APijifengC 的题解。
得到的数肯定都是 的形式,只关心最后得到的系数的集合即可。
考虑这个集合的性质:
- ,即集合中每个元素的绝对值都是 的次幂。
- ,即集合中元素的和为
- 若 或 ,那么 或
- 和 中有一个且仅有一个属于
- 所有指数小于等于 的数的和为
设 表示已经放了 个元素,并且这些元素的和为 ,其中 为现在要放的 的次幂是多少。
枚举选了 个 ,选了 个 ,元素的和变为 。因为下一个填的数就要比现在的数乘 ,所以 转移到 。
需要满足将这些数任意标正负号之后,和为 ,那只需要满足 且
然后考虑对这个系数集合分配原来的 ,这个就是多重组合数的形式 (),把 的部分拆到转移上,即
初始状态为 ,答案 。
signed main()
{
cin >> n;
f[0][N - 1] = 1;
init();
for (rint i = 0; i < n; i++)
{
for (rint j = -n; j <= n; j++)
{
if (f[i][j + N])
{
for (rint a = 0; a <= n; a++)
{
for (rint b = 0; i + a + b <= n; b++)
{
int s = a + b;
int x = i + s;
int y = (j + a - b) >> 1;
if ((a || b) && !((j + a + b) % 2) && abs(j) <= a + b)
{
if (i == 0 && a + b != 1)
{
continue;
}
f[x][y + N] = (f[x][y + N] + f[i][j + N] * inv[a] % P * inv[b]) % P;
}
}
}
}
}
}
cout << f[n][N] * fac[n] % P << endl;
return 0;
}
AGC056D
是奇数时,枚举 Alice 第一次选的数,剩下的数两两匹配,若 Bob 选了其中一个,Alice 就选另一个。
显然,奇数位和它右边的偶数位匹配最优。
设奇数位的和为 ,偶数位的和为 ,此时 Alice 获胜当且仅当 。
是偶数时,仍匹配。枚举 Alice 第一次选的数和这个数匹配的数,剩下的数仍是奇数位匹配右边偶数位最优。
若 Bob 选了这个数匹配的数,Alice 可以新开一个匹配,否则 Alice 选 Bob 选的数匹配的数即可。
想法很好,但代码我写出来的很冗长,就不放了。
AGC027E
考虑对字母进行赋值计算即可。
不妨设, ,显然,操作之后,字符的值之和模 都是固定的。因此,可以便捷的求出之后会变成哪个字符。
考虑 dp 状态, 加上除了自己外的其它 值之和,,其中 表示上一个为 的字符的位置。最后答案为 f[n]
const int N = 1e5 + 5;
const int mod = 1000000007;
int n, ans;
int f[N], p[N], s[N];
char c[N];
bool check(char c[], int n)
{
bool flag = 0;
for (rint i = 1; i < n; i++)
{
if (c[i] == c[i + 1])
{
flag = 1;
}
}
return flag;
}
signed main()
{
cin >> c + 1;
n = strlen(c + 1);
if (!check(c, n))
{
cout << 1 << endl;
return 0;
}
for (rint i = 1; i <= n; i++)
{
s[i] = (s[i - 1] + c[i] - 'a' + 1) % 3;
}
for (rint i = 1; i <= n; i++)
{
if (s[i])
{
f[i]++;
}
f[i] = (f[i] + f[p[0]] + f[p[1]] + f[p[2]] - f[p[s[i]]]) % mod;
p[s[i]] = i;
}
cout << f[n] << endl;
return 0;
}
ARC110E
和 AGC027E 整体做法和思路是一样的
现将字母转换成数字,
给定的串分成好几段,每段异或和不为 ,且不全是同一种字符()
每个段分别合并成一个字符,这样一个合法的划分方案就对应一个答案,注意,一个答案可以由若干个合法方案得到。
记 表示 的后缀 的答案。
转移方程为: , 表示 到 的异或和。,即从 开始最短的一段拼出 的前缀。最后答案为
const int N = 1e6 + 5;
const int mod = 1000000007;
int n;
int c[N], f[N];
int g[N][4];
int tag;
bool check(int c[], int n)
{
bool flag = 0;
for (rint i = 1; i < n; i++)
{
if (c[i] != c[i + 1])
{
flag = 1;
}
}
return flag;
}
signed main()
{
cin >> n;
for (rint i = 1; i <= n; i++)
{
char a;
cin >> a;
c[i] = a - 'A' + 1;
//cout << c[i] << endl;
}
if (!check(c, n))
{
cout << 1 << endl;
return 0;
}
for (rint i = n + 1; i != 0; i--)
{
g[i][c[i]] = i;
g[i][c[i] ^ 1] = g[i + 1][1];
g[i][c[i] ^ 2] = g[i + 1][2];
g[i][c[i] ^ 3] = g[i + 1][3];
tag ^= c[i];
if (tag == 0) f[i] = 1;
if (tag != 0) f[i] = 0;
if (g[i][1])
{
f[i] += f[g[i][1] + 1];
f[i] %= mod;
}
if (g[i][2])
{
f[i] += f[g[i][2] + 1];
f[i] %= mod;
}
if (g[i][3])
{
f[i] += f[g[i][3] + 1];
f[i] %= mod;
}
}
if (tag == 0)
{
cout << f[1] - 1 << endl;
}
else
{
cout << f[1] << endl;
}
return 0;
}
AGC001F
刚开始想的是反向拓扑排序,后来发现时空复杂度是 ,铁定超时。之后用线段树维护,预估复杂度能到 ,然后我写了两个小时没过样例。想看看题解有没有也用线段树+反向拓扑的。然后第一篇 linghuchong_巨佬 的题解用归并排序秒掉了,
构造序列 使得 ,变成交换相邻的差值大于等于 的数。
想实现这个问题可以直接冒泡排序,把大的尽可能往右挪,时空复杂度 。
考虑优化,归并排序。一个右边的数能比左边的数先进行归并就要保证它加 小于等于左边的数的后缀最小值即可。每次归并时记录左边的后缀最小值。对于随机的数据,复杂度甚至接近 。
const int N = 5e5 + 5;
const int inf = 1e9;
int n, k;
int minn[N], pos[N];
int a[N], b[N];
void merge(int l, int r)
{
if (l == r)
{
return ;
}
int mid = (l + r) >> 1;
merge(l, mid);
merge(mid + 1, r);
int _min = inf;
int idx = l - 1;
int x = l, y = mid + 1;
for (rint i = mid; i >= l; i--)
{
_min = min(_min, a[i]),
minn[i] = _min;
}
while (x <= mid && y <= r)
{
if (minn[x] >= a[y] + k)
{
b[++idx] = a[y];
y++;
}
else
{
b[++idx] = a[x];
x++;
}
}
while (x <= mid)
{
b[++idx] = a[x];
x++;
}
while (y <= r)
{
b[++idx] = a[y];
y++;
}
for (rint i = l; i <= r; i++)
{
a[i] = b[i];
}
}
signed main()
{
cin >> n >> k;
for (rint i = 1; i <= n; i++)
{
int x;
cin >> x;
a[x] = i;
}
merge(1, n);
for (rint i = 1; i <= n; i++)
{
pos[a[i]] = i;
}
for (rint i = 1; i <= n; i++)
{
cout << pos[i] << endl;
}
return 0;
}
AGC052D
挺不错的一道题,顺手写个题解交了。
对于一个序列是否能分成两个 LIS 长度相等子序列。我们要注意审题,他说的是两个长度相等的对吧。
也就是说,当对于是整体最长上升子序列长度 , 为偶数,即 !(len % 2)
的时候,这个显然是有解的。
然后呢?先不要着急考虑奇数的情况,对于刚才偶数的情况还没有处理完,这个 怎么计算呢。设 是以 为结尾的最长上升子序列长度,那么不难得出:
计算出 ,就解决了偶数的情况。
接下来考虑奇数情况。 表示以 为开头的最长上升子序列长度,我们不妨设 ,
两个子序列的最长上升子序列长度至少是 ,那么,是不是个 最长上升子序列之外的元素,存在某个长为 的上升子序列覆盖着它呢?
设这个上升子序列 :
对于一个序列,满足 且 的是选取序列,剩下的为剩余序列。
对于无论是选取序列还是剩余序列,我们发现,有不超过 个不同的以 为结尾的最长上升子序列长度对吧。所以,对于选取序列,最长上升子序列长度是要不小于 的,并且剩余序列的最长上升子序列长度同样要不小于 。
所以,求出 ,如果 ,累计一次答案,如果最后累计结果 ,那么就有解,否则无解。
时空复杂度
int T, n;
int p[N], id[N];
int f[N], g[N];
signed main()
{
cin >> T;
while (T--)
{
cin >> n;
for (rint i = 1; i <= n; i++)
{
cin >> p[i];
}
for (rint i = 1; i <= n; i++) id[i] = n + 1;
int len = 0;
for (rint i = 1; i <= n; i++)
{
f[i] = lower_bound(id + 1, id + n + 1, p[i]) - id;
id[f[i]] = p[i];
len = max(len, f[i]);
}
if (!(len % 2))
{
puts("YES");
continue;
}
for (rint i = 1; i <= n; i++) id[i] = n + 1;
for (rint i = n; i != 0; i--)
{
g[i] = lower_bound(id + 1, id + n + 1, n - p[i]) - id;
id[g[i]] = n - p[i];
}
int cnt = 0;
for (rint i = 1; i <= n; i++)
{
if ((f[i] + g[i]) >= (len / 2 + 2))
{
cnt++;
}
}
if (cnt > len)
{
puts("YES");
}
else
{
puts("NO");
}
}
return 0;
}
ARC107D
定义 为选了 个数,和为 。这里不需要考虑分数造成的影响,这种影响在转移方程时会被弥补掉。
考虑如何转移:
- 可以被选,所以所有的数可都翻倍
综上,转移方程为
signed main()
{
cin >> n >> k;
f[0][0] = 1;
for (rint i = 1; i <= n; i++)
{
for (rint j = i; j >= 1; j--)
{
f[i][j] = f[i - 1][j - 1];
int k = j << 1;
if (i >= k)
{
f[i][j] += f[i][k];
}
f[i][j] %= mod;
}
}
cout << f[n][k] << endl;
return 0;
}
ARC159D
表示以 结尾的 LIS 长度。转移方程为 。
然后离散化 和 后用两棵线段树维护 和 的最大值。
AGC007D
表示收到前 个糖果的最小时间,其必然在第 这个位置。
。
对于 ,所有 必然是从头走到尾,所以要加上
时空复杂度
signed main()
{
cin >> n >> m >> t;
for (rint i = 1; i <= n; i++)
{
cin >> a[i];
}
int j = 0;
for (rint i = 1; i <= n; i++)
{
while ((a[i] - a[j + 1]) * 2 >= t && j < n)
{
minn = min(minn, f[j] - 2 * a[j + 1]);
j++;
}
f[i] = min(2 * a[i] + minn, f[j] + t);
}
cout << f[n] + m << endl;
return 0;
}
ARC064E
圆外点到圆内的距离最短:和圆心连线。
将所有圆都看作圆心一个点,求出的距离必定是最短距离。
给任意两点间建边,边权就是圆心距离减去两个圆的半径,之后最短路即可。
邻接矩阵朴素 djkstra 就能过,没必要堆优化,时空复杂度
signed main()
{
cin >> x[0] >> y[0] >> x[1] >> y[1] >> n;
for (rint i = 0; i < n; i++)
{
cin >> x[i + 2] >> y[i + 2] >> r[i + 2];
}
n += 2;
for (rint i = 0; i < n; i++)
{
for (rint j = i + 1; j < n; j++)
{
double d = sqrt((x[j] - x[i]) * (x[j] - x[i]) + (y[j] - y[i]) * (y[j] - y[i]));
a[i][j] = a[j][i] = max(0.0, d - r[i] - r[j]);
}
}
for (rint i = 1; i < n; i++)
{
dist[i] = inf;
}
for (rint j = 0; j < n; j++)
{
int x = -1;
for (rint i = 0; i < n; i++)
{
if (!v[i] && (x == -1 || dist[i] < dist[x]))
{
x = i;
}
}
v[x] = 1;
for (rint i = 0; i < n; i++)
{
dist[i] = min(dist[i], dist[x] + a[x][i]);
}
}
cout << fixed << setprecision(15) << dist[1] << endl;
return 0;
}
本文作者:PassName
本文链接:https://www.cnblogs.com/spaceswalker/p/17808350.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步