Toyota Programming Contest 2024#3(AtCoder Beginner Contest 344)
C
先预处理出三个数组能拼出的数,存放到 map
中。
查询的时候只需要看这个数是否出现在 map
里即可。
时间复杂度 ,, 是 map
的时间复杂度。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5 + 5;
int n, m, l, q, a[N], b[N], c[N], d[N];
map<int, int> vis;
signed main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
cin >> m;
for (int i = 1; i <= m; i++) cin >> b[i];
cin >> l;
for (int i = 1; i <= l; i++) cin >> c[i];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
for (int k = 1; k <= l; k++) {
vis[a[i] + b[j] + c[k]] = 1;
}
}
}
cin >> q;
for (int i = 1; i <= q; i++) {
cin >> d[i];
if (vis[d[i]]) puts("Yes");
else puts("No");
}
}
D
考虑 dp。定义 表示前 个组拼成所给字符串前 个字符的最小花费。
对第 个组进行扫描,看这一个字符串能不能拼上去。
比赛时没有精细实现,所以代码看看就好了:
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 3e5 + 5;
char a[N];
int m, cnt[105], dp[205][205];
string s[105][105];
signed main() {
memset(dp, 0x3f, sizeof dp);
dp[0][0] = 0;
cin >> (a + 1);
int n = strlen(a + 1);
cin >> m;
for (int i = 1; i <= m; i++) {
cin >> cnt[i];
for (int j = 1; j <= cnt[i]; j++) cin >> s[i][j];
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= cnt[i]; j++) {
int len = s[i][j].size();
for (int k = 0; k <= n; k++) {
if (k + len > n) continue;
string tmp = "";
for (int w = k + 1; w <= k + len; w++) tmp += a[w];
for (int w = 0; w <= m; w++) {
if (tmp == s[i][j] && i > w) dp[k + len][i] = min(dp[k + len][i], dp[k][w] + 1);
}
}
}
}
int res = 2e9;
for (int i = 1; i <= m; i++) res = min(res, dp[n][i]);
if (res == 2e9) puts("-1");
else cout << res << endl;
}
E
问题陈述
给你一个长度为 的序列 。 中的元素是不同的。
请按照给出的顺序处理 个查询。每个查询属于以下两种类型之一:
1 x y
:在 中元素 后面紧接着插入 。当给出此查询时,保证 存在于 中。2 x
:从 中删除元素 。当给出此查询时,保证 存在于 中。
保证在处理完每一个查询后, 都不是空的,并且其元素都是不同的。
处理完所有查询后,打印 。
思路
这道题为什么可以用链表做呢?因为这道题只需要最后输出整个序列,而不是实时查询。题目保证 中的元素是不同的,这也是可以用链表的核心。
所以链表写扩展性极低,所以比赛时写的分块。为什么某某部分人都写链表呢?不言而喻。俺就不写链表!但是又想到有个 rope
的玩意,于是很开心的写出了以下代码:
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#include <ext/rope>
using namespace __gnu_cxx;
using namespace std;
const int N = 4e5 + 5;
int n, a[N];
rope<int> b;
signed main() {
scanf("%d", &n);
b.push_back(0);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
b.push_back(a[i]);
}
int q;
scanf("%d",&q);
while (q--) {
int op, x, y;
scanf("%d%d",&op,&x);
if (op == 1) {
scanf("%d",&y);
b.insert(b.find(x)+1, y);
}
else {
b.erase(b.find(x), 1);
}
}
int t = b.size();
for (int i = 1; i < t; i++) printf("%d ", b.at(i));
}
这份代码只过了 个点,不知道是 rope 的 find 函数是 的,还是常数太大。
于是又去写分块了。
而分块思路简单,代码好调好写,清晰明了,常数小,大部分链表代码跑不过分块。
考虑把序列分成 块,每块用一个 vector
动态维护,即把这个块的所有数都放到对应的 vector
里面。在某个元素后面插入一个数时,只需要在这个元素的对应块的 vector
中插入,由于每块的 vector
大小只有 ,所以插入的时间复杂度只有 。删除一个数也是同样的道理。
但是,真的能保证每个 vector
都是 的大小吗?如果我们在固定一个位置加进很多个数,可能对应 vector
的大小就达到了 级别。所以我们考虑块重构。即对整个序列重新分组,使得所有 vector
的大小变平均。
什么时候可以块重构?块重构是需要消耗 的时间的,显然不能加一个数重构一次,可以间隔 次重构一次,块重构复杂度 。
总的时间复杂度也是 ,事实上,vector 的 insert 非常快,所以不比链表慢多少。
注意事项:
-
题目不是在某个位置后面添加一个数,而是在某个元素后面添加一个数,所以需要开一个数组 代表 在序列的第几个块内。
-
本题值域较大,用 数组需要离散化!用 map 复杂度就会变成 ,会 TLE!
// 只用了394ms
#include <bits/stdc++.h>
using namespace std;
const int N = 6e5 + 5, M = 2000;
int n, q, a[N], lx[N], rx[N], len, b[N], tot;
int tmp[N], tl, sum[M], bel[N];
vector<int> bk[M];
// bk_i 存储这个块内的数
// lx_i,rx_i存储这个块的左右端点
// bel存储这个数所在的块
struct edge {
int op, l, r;
}ed[N];
signed main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]), b[++tot] = a[i];
int sq = sqrt(n);
len = n / sq;
for (int i = 1; i <= n / sq; i++) lx[i] = (i - 1) * sq + 1, rx[i] = lx[i] + sq - 1;
if (n % sq) {
len++;
lx[len] = rx[len - 1] + 1;
rx[len] = n;
}
int cnt = 0;
scanf("%d", &q);
for (int i = 1; i <= q; i++) {
scanf("%d", &ed[i].op);
if (ed[i].op == 1) scanf("%d%d", &ed[i].l, &ed[i].r), b[++tot] = ed[i].l, b[++tot] = ed[i].r;
else scanf("%d", &ed[i].l);
}
sort(b + 1, b + tot + 1);
tot = unique(b + 1, b + tot + 1) - b - 1;
for (int i = 1; i <= n; i++) a[i] = lower_bound(b + 1, b + tot + 1, a[i]) - b;
for (int i = 1; i <= q; i++) {
if (ed[i].op == 1) ed[i].r = lower_bound(b + 1, b + tot + 1, ed[i].r) - b;
ed[i].l = lower_bound(b + 1, b + tot + 1, ed[i].l) - b;
}
for (int i = 1; i <= len; i++) {
for (int j = lx[i]; j <= rx[i]; j++) bk[i].push_back(a[j]), bel[a[j]] = i;
}
for (int cas = 1; cas <= q; cas++) {
int op = 0, l = 0, r = 0;
op = ed[cas].op;
cnt++;
if (op == 1) {
l = ed[cas].l, r = ed[cas].r;
int L = bel[l], lenb = bk[L].size(); // L是l所在的块
for (int i = 0; i < lenb; i++) {
if (bk[L][i] == l) {
l = i; // 扫描到l的位置
break;
}
}
if (l == lenb - 1) bk[L].push_back(r);
else bk[L].insert(bk[L].begin() + l + 1, r);
bel[r] = L;
rx[L]++;
for (int i = L + 1; i <= len; i++) lx[i]++, rx[i]++;
}
if (op == 2) {
l = ed[cas].l;
int L = bel[l], lenb = bk[L].size();
for (int i = 0; i < lenb; i++) {
if (bk[L][i] == l) {
l = i;
break;
}
}
bel[bk[L][l]] = 0;
bk[L].erase(bk[L].begin() + l);
rx[L]--;
for (int i = L + 1; i <= len; i++) lx[i]--, rx[i]--;
}
if (cnt >= 5 * sq) { // 一定次数后块重构
tl = 0;
for (int i = 1; i <= len; i++) {
for (auto j : bk[i]) tmp[++tl] = j;
}
sq = sqrt(tl);
len = tl / sq;
for (int i = 1; i <= tl / sq; i++) {
lx[i] = (i - 1) * sq + 1, rx[i] = lx[i] + sq - 1;
}
if (tl % sq) {
len++;
lx[len] = rx[len - 1] + 1;
rx[len] = tl;
}
for (int i = 1; i <= len; i++) {
bk[i].clear();
for (int j = lx[i]; j <= rx[i]; j++) bk[i].push_back(tmp[j]), bel[tmp[j]] = i;
}
cnt = 0;
}
}
for (int i = 1; i <= len; i++) {
if (bk[i].size() == 0) continue;
for (auto v : bk[i]) printf("%d ", b[v]);
}
}
F
update at 2023/3/10:更新了一些正确性说明。
场切。贪心+动态规划。
先把图建出来,可以将问题转化为在图上行走,抽象为如下问题(这是原题的严格强化):
一个人在一张有向图的 号结点,他要去到 结点。每条边 有边权 ,表示走过这条边需要花 元。这个人一开始有 元,到了一个点 ,他可以进行若干次停留,每次停留收获 元。问到达 的最小停留次数,并将答案加上 。若无解输出
-1
。
为什么答案要加 ,因为我们只算了停留次数。但是中间行走的步数是确定的为 。
考虑贪心,考虑从 点走到 点( 不一定是一条边),且在 两点都赚钱停留。如果 停留一次赚的钱比 停留一次赚的钱多,那么不如不在 这里停留(不如在 点先把钱赚了)。那么就先将 从小到大排序,路径中停留的点的 是递增关系,如果当前点走不到比它更赚钱的点,直接走到 号点。
预处理出 代表 走到 的最小路径长度(边权)。
从 号点赚钱完毕走到 去赚钱,是把钱赚够就走(赚够代表当前的钱可以支付 ),还是可以在 号点多赚一点,并使得到达 号点时钱更多?
答案是赚够就走,因为在 点多赚一点钱,不如走到 号点去赚,因为 。
既然赚够就走,那么 到 的路径,到达 点后有许多种状态,用二元组 表示。 代表到达 点后停留次数, 代表到达 点后所剩的钱。到底哪种方案更优?停留次数 最小的更优?赚的钱 越多更优?停留次数越少的答案越少,赚的钱越多后面就能越少停留。
考虑 和 两种状态,若 且 ,此时我们怎么判断谁更优?
事实上, 只需要到达 后再多停留一次,所剩余的钱就比 多。
因为 ( 是因为赚够就走),所以 。这就说明 优于 。
综上:停留次数越少,方案越优。如果停留次数相等,则剩的钱越多越优。
于是直接定义 代表 号点的最少停留次数与最多赚钱数,按照 w 从小到大的顺序转移即可。
复杂度 。但是,这道题不排序也是对的,可能本题是一个拓扑图的缘故。
#include <bits/stdc++.h>
using namespace std;
#define PII pair<int, int>
#define int long long
const int N = 6405;
int T, n, m, p, w[N], f[N], g[N], st[N];
// f_i,g_i 即为i号点(最少停留次数,最多赚钱数)
int dist[N][N];
vector<pair<int, int> > G[N];
struct edge {
int x, id;
}a[N];
bool cmp(edge x, edge y) { return x.x < y.x; }
void dijstra(int s) {
priority_queue<PII, vector<PII>, greater<PII> > q;
memset(st, 0, sizeof st);
dist[s][s] = 0; q.push({0, s});
while (q.size()) {
auto t = q.top();
q.pop();
int u = t.second;
if (st[u]) continue;
st[u] = 1;
for (auto e : G[u]) {
auto v = e.first, d = e.second;
if (dist[s][v] > dist[s][u] + d) {
dist[s][v] = dist[s][u] + d;
q.push({dist[s][v], v});
}
}
}
}
void change(int x, int nf, int ng) {
if (nf < f[x]) f[x] = nf, g[x] = ng;
else if (nf == f[x] && ng > g[x]) f[x] = nf, g[x] = ng;
}
int get(int x, int y) {
return (x - 1) * n + y;
}
signed main() {
memset(dist, 0, sizeof dist);
cin >> n; p = 0;
for (int i = 1; i <= n * n; i++) {
for (int j = 1; j <= n * n; j++) {
if (i == j) dist[i][j] = 0;
else dist[i][j] = 1e18;
}
}
for (int i = 1; i <= n * n; i++) cin >> w[i], a[i].x = w[i], a[i].id = i;
sort(a + 1, a + n * n + 1, cmp);
a[n * n + 1].x = 0, a[n * n + 1].id = n * n; // 要加入 n 号点,因为如果一个点走不到比它更赚钱的点,直接走到 n 好点
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n - 1; j++) {
int x;
cin >> x;
G[get(i, j)].push_back({get(i, j + 1), x});
}
}
for (int i = 1; i < n; i++) {
for (int j = 1; j <= n; j++) {
int x;
cin >> x;
G[get(i, j)].push_back({get(i + 1, j), x});
}
}
for (int i = 1; i <= n * n; i++) {
dijstra(i); // 预处理最短路,当然,这是一个拓扑图,直接递推也可以
}
for (int i = 1; i <= n * n; i++) f[i] = 1e18, g[i] = 0;
f[1] = 0, g[1] = p;
for (int i = 1; i <= n * n; i++) {
for (int j = i + 1; j <= n * n + 1; j++) {
int x = a[i].id, y = a[j].id;
if (dist[x][y] == 1e18) continue;
if (g[x] >= dist[x][y]) change(y, f[x], g[x] - dist[x][y]); // 如果钱足够,直接走
else {
int t = dist[x][y] - g[x];
int k = (t + w[x] - 1) / w[x];
change(y, f[x] + k, g[x] + k * w[x] - dist[x][y]);
}
}
}
if (f[n * n] <= 3e12) cout << f[n * n] + 2 * n - 2 << endl;
else puts("-1");
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧