2024-01-19 15:41阅读: 201评论: 0推荐: 0

AtCoder Beginner Contest 335

A - 2023 (abc335 A)

题目大意

给定一个字符串,将最后一位改成4

解题思路

模拟即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
string s;
cin >> s;
s.back() = '4';
cout << s << '\n';
return 0;
}


B - Tetrahedral Number (abc335 B)

题目大意

给定n,按字典序输出非负整数i,j,k,使得 i+j+kn

解题思路

n只有 21,直接 O(n3)枚举判断即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
for (int i = 0; i <= n; ++i)
for (int j = 0; j <= n; ++j)
for (int k = 0; k <= n; ++k)
if (i + j + k <= n)
cout << i << ' ' << j << ' ' << k << '\n';
return 0;
}


C - Loong Tracking (abc335 C)

题目大意

二维网格,贪吃蛇,移动,进行q次操作,分两种

  • 指定贪吃蛇下一步移动的方向
  • 指定i,输出贪吃蛇的第 i个部位的坐标。

解题思路

每移动一次,只有头部到了新的坐标,其他部分的坐标都变成前一个。

如果我们把每个部分的坐标按顺序放在一个队列里,队尾是头部坐标,队头是尾部坐标,每次移动相当于一次出队和一次入队。

stl的队列queue不支持随机访问,因此用vector模拟这个队列即可。

出队其实没有必要操作,不断push_back即可。问第i个部位坐标就从队尾倒着数即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, q;
cin >> n >> q;
vector<array<int, 2>> pos(n);
for (int i = 0; i < n; ++i) {
pos[i] = {n - i, 0};
}
map<char, array<int, 2>> dir{
{'U', {0, 1}}, {'D', {0, -1}}, {'L', {-1, 0}}, {'R', {1, 0}}};
while (q--) {
int op;
cin >> op;
if (op == 1) {
string s;
cin >> s;
auto [x, y] = pos.back();
auto [dx, dy] = dir[s[0]];
x += dx;
y += dy;
pos.push_back({x, y});
} else if (op == 2) {
int s;
cin >> s;
auto [x, y] = *(pos.end() - s);
cout << x << ' ' << y << '\n';
}
}
return 0;
}


D - Loong and Takahashi (abc335 D)

题目大意

给定一个n×n的二维网格,n奇数,给每个格子一个数字[1,n21],要求每个数字仅使用一次,且相邻数字的格子相邻,且正中间的格子不能是数字,而是T

给出一种构造方法。

解题思路

按照样例的构造方式,一个螺旋回字构造即可。

每次一个螺旋回字构造就填充最外围的一圈,起点依次为(1,1)(2,2)(3,3) ,最后恰好到正中间,因此能恰好填满。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
int mid = n / 2;
vector<vector<int>> tu(n, vector<int>(n, 0));
int num = 1;
array<array<int, 2>, 4> dir{{{0, 1}, {1, 0}, {0, -1}, {-1, 0}}};
int cur = 0;
int x = 0, y = 0;
while (x != mid || y != mid) {
tu[x][y] = num;
auto [dx, dy] = dir[cur];
int nx = x + dx, ny = y + dy;
while (nx < 0 || nx >= n || ny < 0 || ny >= n || tu[nx][ny] != 0) {
cur = (cur + 1) % 4;
auto [dx, dy] = dir[cur];
nx = x + dx, ny = y + dy;
}
++num;
x = nx, y = ny;
}
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j) {
if (i == mid && j == mid)
cout << 'T' << ' ';
else
cout << tu[i][j] << " \n"[j == n - 1];
}
return 0;
}


E - Non-Decreasing Colorful Path (abc335 E)

题目大意

给定一张无向图,点有点权。

找一条从1n的路径,其点权不递减,且不同的数的个数最多。不存在则输出0

解题思路

注意到这个路径要求点权是一个不严格递增的情况,这明显存在一个决策的方向性,即点权大的一定从点权小的求得答案,当点权小的答案都求完了,那么该点权大的答案也求完了,即固定了,知晓了。

因此就直接设dp[i]表示到达点 i时的最大的答案(不同的数的个数最多),按照点权从小到大转移,若点权相同则按照答案从大到小转移。用优先队列维护转移顺序即可。往后转移。初始条件为dp[1]=1

也可以根据点权构造边权,注意到图如果有环,其边权一定都是0,缩环后变成一张DAG(有向无环图),直接拓扑 DP即可,其实跟上面是一样的。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int inf = 1e9;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, m;
cin >> n >> m;
vector<int> a(n);
for (auto& i : a)
cin >> i;
vector<vector<array<int, 2>>> edge(n);
auto add = [&](int u, int v) {
if (a[u] <= a[v])
edge[u].push_back({v, -(a[u] != a[v])});
};
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
--u, --v;
add(u, v);
add(v, u);
}
vector<int> dis(n, inf);
dis[0] = -1;
priority_queue<array<int, 3>> q;
q.push({-a[0], -dis[0], 0});
while (!q.empty()) {
auto [_, d, u] = q.top();
q.pop();
if (dis[u] != -d)
continue;
for (auto [v, w] : edge[u]) {
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
q.push({-a[v], -dis[v], v});
}
}
}
debug(dis);
cout << max(0, -dis.back()) << '\n';
return 0;
}


F - Hop Sugoroku (abc335 F)

题目大意

给定一个n个数的数组a, 你在0处。当你在 i处时,你可以移动到 j=i+ai×k<n处,k为正整数,也可以不移动。

问你可以移动到的位置的集合数量。

解题思路

注意到每次移动一定是往右边移动,具有明显的决策方向性,可以设dp[i]表示移动到第 i个位置时,移动位置的集合数量(即这个集合中的最大值为 i的集合数量)。

转移式则为dp[i]=j%aj==i%ajdp[j],初始条件为 dp[0]=1

上述dp的复杂度为 O(n2),考虑如何优化转移。

但思考下会发现上述转移最坏情况就是 O(n),当 aj全部为1时 ,就要对每个j都累加。因为是对每个 j都累加,事实上我们可以预先处理出前缀和,这样就可以 O(1)

考虑这个前缀和的形式是怎样的,我们对aj进行了枚举,假设为 x,那满足转移条件的 j 则为j%x==i%x=y,我们要把这些 jdp[j]累加,即 sum[x][y]=j%x==ydp[j] ,这样dp[i]=x=1maxasum[x][i%x]

但因为maxa2e5,和n同一个数量级,上述转移 复杂度还是O(n2)

但注意到如果aj比较大时,满足条件的j 的数量是很少的,这种情况下我们可以暴力转移,这个转移是往后转移,即当前为i,则 dp[j]+=dp[i](j=i+ai×k)

这期间就有个分界点,我们设分界点为mid=n

  • ai>mid时,我们暴力更新后面的dp[j],这样的j的数量(即 k的数量)不超过nn=n个。更新的复杂度是O(nn)
  • aimid时,由于转移的数量 j很多,我们就先不转移,先保存在 sum数组里(可以看成是懒标记),即 sum[ai][i%ai]+=dp[i]。更新的复杂度是O(1)

这样,对于后续的一个idp[i]的值,注意到原始转移式是dp[i]=j%aj==i%ajdp[j],我们已经把情况一(即aj>mid)的dp[j]累加进 dp[i]里了,还剩下ajmid的没累加进来,因此此时再 dp[i]+=j=1midsum[j][i%j]即可。更新的复杂度是O(nn)

最终的时间复杂度是O(nn)

注意到上述转移里,我们把jaj是割裂开了,考虑 aj的所有取值情况,然后分成两类分别处理。

这其实也是一类经典的dp优化方法,即根号分治dp,即存在两种形式的转移,且在不同数量级下时间复杂度各有优势,然后结合起来, n是一个经典的分界点

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int mo = 998244353;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin >> n;
int half = sqrt(n);
vector<vector<int>> sum(half, vector<int>(half, 0));
vector<int> a(n);
for (auto& i : a)
cin >> i;
vector<int> dp(n);
dp[0] = 1;
for (int i = 0; i < n; ++i) {
for (int j = 1; j < half; ++j) {
dp[i] += sum[j][i % j];
if (dp[i] >= mo)
dp[i] -= mo;
}
if (a[i] >= half) {
for (int j = i + a[i]; j < n; j += a[i]) {
dp[j] += dp[i];
if (dp[j] >= mo)
dp[j] -= mo;
}
} else {
sum[a[i]][i % a[i]] += dp[i];
if (sum[a[i]][i % a[i]] >= mo)
sum[a[i]][i % a[i]] -= mo;
}
}
int ans = 0;
for (auto& i : dp) {
ans += i;
if (ans >= mo)
ans -= mo;
}
cout << ans << '\n';
return 0;
}


G - Discrete Logarithm Problems (abc335 G)

题目大意

给定一个数组a和一个质数P,求 (i,j)的数量,满足以下条件

  • 存在 k,使得 aikaj(modP)

解题思路

这题难在需要有数论知识。

首先因为有质数P,会存在一个原根 g,使得 g0gP2的值恰好取遍了 1P1 ,由费马小定理知gP1g01(modP)就回到了起点。上述的幂是对P取模的。

因此ai可以找到个 x,使得 gxai(modP),对应的aj则对应y

除此之外,关于阶的概念:一个数的阶是一个最小的正整数m,使得 aim1(modP) ,其实就是循环节的长度,一般记为ord(ai)

然后是一个简单但深刻的定理:

  • 如果存在k,使得aikaj(modP),则有 ord(aj)|ord(ai)

如何通俗点理解呢?考虑阶的定义,gxm1g0(modP),关心指数部分的话,即为 xm0(modP1) ,即xm=n(P1)

注意到m是最小的,怎么最小呢?我们要让 xmP1的倍数,我们将 xP1质因数分解, x缺少的那些质因数就是造成x不是 P1的倍数的原因,这缺少的部分就由 m补上,那这个 m就是最小的了。注意这里的m就是一个数的阶order。它就是 x相对于 P1缺少的那部分质因数,所以也必定是 P1的因子

这里得到了另一个简单但也深刻的定理,也是最后求一个数的阶的理论依据:

  • ord(ai)|P1

对于 aj来说,就是 ym=kxm=n(P1)。我们知道xm已经是 P1的倍数了,那 kxm也必定是 P1的倍数,但这里的m不一定是最小的,因为 k也带来了一些 P1的质因数,因此 m会比 m更小,它去掉了 k包括的 P1的一些质因数,也即 m|m,即ord(aj)|ord(ai)

因此,如果我们求出了每个ai的阶,剩下的就是求一个数的倍数有多少个。

如何快速求一个数的阶呢?注意到 ord(ai)|P1,我们先假设其阶m=P1,此时必有 aiP11(modP),但此时可能不是最小的,因为关于 P1的质因数有多余的(跟上面的 kxmm不是最小的一个道理),我们要把多余的质因数去掉。

如何去除呢?其实只要枚举去除哪个质数,去除该质数的指数是多少即可。当去除的过多时,就不满足aim1(modP) ,但还能去除时,则还会满足该等式。所谓最小的m,意味着任何一个更小的数,都不会满足该等式。

注意到 P只有 1e13,质因数种类最多只有 logP个,每个质数的幂最大也只有 logP ,因此可以在O(log3P)的复杂度内求出一个数的阶(还有一个log是快速幂)。

因此一开始先对P1质因数分解,然后求出每个数的阶,此时显然不能O(n2)枚举阶,判断倍数关系。

注意到 P1只有1013,最多只有 12个左右的质数,因此其因数个数最多只有 212,是d=O(103)的数量级,因为阶一定是P1的倍数,虽然有 O(n)个,但不同的阶的数量不超过 O(d),因此我们可以统计每个阶的数量,直接 O(d2)枚举每个阶的数量,判断倍数累加答案即可。

注意判阶数时的快速幂可能会爆long long

最终的时间复杂度是O(nlog3P)+d2(P1))

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
long long qpower(__int128 a, __int128 b, long long mo) {
__int128 qwq = 1;
while (b) {
if (b & 1)
qwq = qwq * a % mo;
a = a * a % mo;
b >>= 1;
}
return qwq;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
LL mo;
cin >> n >> mo;
LL phi = mo - 1, tmp = phi;
vector<LL> a(n);
for (auto& i : a)
cin >> i;
vector<LL> prime;
int half = sqrt(phi) + 1;
for (int i = 2; i <= half; ++i) {
if (phi % i == 0) {
prime.push_back(i);
while (phi % i == 0)
phi /= i;
}
}
if (phi != 1)
prime.push_back(phi);
phi = tmp;
map<LL, int> cnt;
auto order = [&](LL x) {
LL m = phi;
for (auto& i : prime) {
while (m % i == 0 && qpower(x, m / i, mo) == 1) {
m /= i;
}
}
return m;
};
for (auto& i : a) {
cnt[order(i)]++;
}
LL ans = 0;
for (auto& i : cnt) {
for (auto& j : cnt) {
if (i.first % j.first == 0) {
ans += 1LL * i.second * j.second;
}
}
}
cout << ans << '\n';
return 0;
}


本文作者:~Lanly~

本文链接:https://www.cnblogs.com/Lanly/p/17974808

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   ~Lanly~  阅读(201)  评论(0编辑  收藏  举报
历史上的今天:
2017-01-19 多叉树转换二叉树
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.