ABC 369

刚才翻上次写的 abc 366 题解, 发现语言挺抽象, 导致自己都快看不懂了, 这回写好点

这段时间第一次 Rated, 情况一般吧, F 忘给同一个 x 的所有 y 排序了, 今天 (9.1) 早上突然看出来了。G 没有细看, 以为是个博弈论, 现在才发现是个简单贪心

369 这数挺吉利哈哈, 济南好像有说法是初三初六初九宜出行, 济南公交也有个 app 叫 369。这次就当作重新学 oi 的开始了, 369, 宜踏上新的征程!

A

题意

给定 a,b, 求一个 x, 使得三个数满足 qp=rq 的形式(a,b,x 顺序可变, 同一个 (q,p,r) 只算一次)

解答

直接分类讨论即可, 重点是 a,b,x 存在至少两个数相等时怎么办

更简单的写法是直接枚举 x, 范围 [200,200] , map 判一下重的 (q,p,r) 即可

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10, MOD = 998244353;
int a, b;
int ans;
signed main(){
// freopen("1.in", "r", stdin);
cin >> a >> b;
++ans;
if((a + b) / 2 != a && (a + b) / 2 != b && (a + b) % 2 == 0) ++ans;
if(a != b) ++ans;
cout << ans << "\n";
}

B

题意

弹钢琴, 左右手相互独立, 按顺序给出每一步左/右手需要到达的位置坐标。

定义疲劳度为两坐标的差的绝对值, 手的起始位置由你决定, 求最小疲劳度

解答

这题有点诈骗啊

首先每步操作按顺序给出, 不能改变

然后起始位置设成左/右手分别的第一步操作的坐标即可, 剩下的是语法组内容

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10, MOD = 998244353;
int n, v;
char opt;
vector <int> l, r;
signed main(){
// freopen("1.in", "r", stdin);
cin >> n;
for(int i = 1; i <= n; i++){
cin >> v >> opt;
if(opt == 'L') l.push_back(v);
else r.push_back(v);
}
// sort(l.begin(), l.end());
// sort(r.begin(), r.end());
int ans = 0;
for(int i = 1; i < l.size(); i++){
ans += abs(l[i] - l[i - 1]);
}
for(int i = 1; i < r.size(); i++){
ans += abs(r[i] - r[i - 1]);
}
cout << ans << "\n";
}

C

题意

给定正整数序列, 求有序不可重数对 (l,r) 的个数, 使得 al...ar 是等差数列(l,r 可以相等)

解答

判断等差数列常见套路:差分相等

然后就没了

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10, MOD = 998244353;
int n;
int a[N], cha[N];
signed main(){
// freopen("1.in", "r", stdin);
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i];
cha[i] = a[i] - a[i - 1];
}
int lst = cha[2], cnt = 1, ans = n;
for(int i = 3; i <= n; i++){
if(cha[i] == lst){
++cnt;
}else{
ans += (1 + cnt) * cnt / 2;
cnt = 1;
lst = cha[i];
}
}
if(n != 1) ans += (1 + cnt) * cnt / 2;
cout << ans << "\n";
}

D

题意

打怪, 打死每只怪物可以获得一定的经验, 必须按顺序, 但可以跳过一些怪物

对于你打死的第 i 只怪物, 如果 i 是偶数, 可以获得双倍经验

解答

小 DP

odd(i) 表示考虑到第 i 只怪物, 一共打死了奇数只, 获得的最大经验, even(i) 同理

每次转移是 O(1)

odd[i] = max(odd[i - 1], even[i - 1] + a[i]);
even[i] = max(even[i - 1], odd[i - 1] + a[i] * 2);

但是边界要稍微处理一下, 我因此 Wa 了一发

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10, INF = 1e18;
int n;
int a[N], odd[N], even[N];
signed main(){
// freopen("1.in", "r", stdin);
cin >> n;
for(int i = 1; i <= n; i++){
cin >> a[i];
}
odd[0] = -INF, even[0] = 0;
for(int i = 1; i <= n; i++){
odd[i] = max(odd[i - 1], even[i - 1] + a[i]);
even[i] = max(even[i - 1], odd[i - 1] + a[i] * 2);
}
int ans = max(odd[n], even[n]);
cout << ans << "\n";
}

E

题意

有一些节点和双向道路。

你的目标是从 1 走到 n, 每次指定 O(1) 条道路, 完成上述目标的同时必须经过这些道路至少一次, 求最短距离

解答

只限定了 5 条道路, 3e3 次询问, 很难不想着钻空子

不妨枚举经过这些道路的顺序, 共 5 种, 再枚举每条路从 uv 还是反过来, 25 种, 发现完全可过

然后就完事了, 最短路 Floyd 都可以做, 代码复杂度极低哈哈哈

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 4e2 + 10, M = 2e5 + 10, INF = 1e18;
int n, m;
int u, v, w;
int e[N][N], mini;
int q, k, b[N];
struct Edge{
int u, v, w;
}rec[M];
void Floyd(){
for(int k = 1; k <= n; k++){
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
e[i][j] = min(e[i][j], e[i][k] + e[k][j]);
}
}
}
}
void Dfs(int x, int st, int dis){
if(x == k + 1){
mini = min(mini, dis + e[st][n]);
return;
}
Dfs(x + 1, rec[b[x]].v, dis + e[st][rec[b[x]].u] + rec[b[x]].w);
Dfs(x + 1, rec[b[x]].u, dis + e[st][rec[b[x]].v] + rec[b[x]].w);
}
signed main(){
// freopen("1.in", "r", stdin);
cin >> n >> m;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
e[i][j] = INF;
}
e[i][i] = 0;
}
for(int i = 1; i <= m; i++){
cin >> u >> v >> w;
rec[i] = {u, v, w};
e[u][v] = min(e[u][v], w);
e[v][u] = e[u][v];
}
Floyd();
cin >> q;
for(int i = 1; i <= q; i++){
cin >> k;
for(int j = 1; j <= k; j++){
cin >> b[j];
}
mini = INF;
do{
Dfs(1, 1, 0);
}while(next_permutation(b + 1, b + 1 + k));
cout << mini << "\n";
}
}

F

题意

网格图上行走, 只能往右或往下, 其中一些格点打了标记。

问你从 (1,1) 走到 (h,w) 的过程中最多经过多少标记点, 并且用 D,R 的方式输出一种行走方案

网格图的大小是 2e52e5 的, 总共 2e5 个标记点

解答

如果格子小一点, 那么就是个普及组题。既然这样, 那么我们的算法肯定是基于标记点个数而非整个网格的。

考虑 ”传统“ 的转移:

dp(i,j)=max{dp(ii,jj)}+1

(ii,jj)(i,j)

所以想起了二维问题的常见处理思路:固定一维扫另一维

从上到下扫过 x 轴, 枚举当前 x 的对应的所有 y (这里一定要排序!!!, 我因此 Wa 了两发)。

对于确定的 y , 求出 yyy 对应的最大 dp 值, 因为固定了 x 的顺序是从上到下, 所以每一个 y 内存储的信息都来自于小于当前 xx, 开个线段树维护一下即可

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e7 + 10, INF = 1e18;
int h, w, n;
int r[N], c[N], ir[N], ic[N], cnt;
map <int, int> rec, rcr, rcc;
int rr[N], cc[N], cntr, cntc;
int pre[N];
vector <int> ans;
vector <pair <int, int>> vec[N];
struct Segment_Tree{
struct Node{
int l, r;
int maxi, id;
}t[N];
void Build(int id, int l, int r){
t[id] = {l, r, 0};
if(l == r) return;
int mid = (l + r) / 2;
Build(id * 2, l, mid);
Build(id * 2 + 1, mid + 1, r);
}
void Modify(int id, int l, int r, int x, int idx){
if(r < t[id].l || t[id].r < l) return;
if(l <= t[id].l && t[id].r <= r){
// t[id].maxi = max(t[id].maxi, x);
if(x > t[id].maxi){
t[id].maxi = x;
t[id].id = idx;
}
return;
}
Modify(id * 2, l, r, x, idx), Modify(id * 2 + 1, l, r, x, idx);
if(t[id * 2].maxi > t[id * 2 + 1].maxi){
t[id].maxi = t[id * 2].maxi;
t[id].id = t[id * 2].id;
}else{
t[id].maxi = t[id * 2 + 1].maxi;
t[id].id = t[id * 2 + 1].id;
}
}
pair <int, int> Query(int id, int l, int r){
if(r < t[id].l || t[id].r < l) return {0, 0};
if(l <= t[id].l && t[id].r <= r){
return {t[id].maxi, t[id].id};
}
auto a = Query(id * 2, l, r);
auto b = Query(id * 2 + 1, l, r);
if(b.first > a.first){
a = b;
}
return a;
}
}t;
signed main(){
// freopen("1.in", "r", stdin);
// freopen("1.out", "w", stdout);
cin >> h >> w >> n;
for(int i = 1; i <= n; i++){
cin >> r[i] >> c[i];
rr[++cntr] = r[i];
cc[++cntc] = c[i];
}
sort(rr + 1, rr + 1 + cntr);
sort(cc + 1, cc + 1 + cntc);
int ca = 0;
for(int i = 1; i <= cntr; i++){
if(!rcr[rr[i]]) rcr[rr[i]] = ++ca;
}
int cb = 0;
for(int i = 1; i <= cntc; i++){
if(!rcc[cc[i]]) rcc[cc[i]] = ++cb;
}
for(int i = 1; i <= n; i++){
vec[rcr[r[i]]].push_back({rcc[c[i]], i});
}
t.Build(1, 1, cb);
for(int i = 1; i <= ca; i++){
sort(vec[i].begin(), vec[i].end());
for(auto j : vec[i]){
auto v = t.Query(1, 1, j.first);
pre[j.second] = v.second;
t.Modify(1, j.first, j.first, v.first + 1, j.second);
}
}
// cout << t.Query(1, 1, cb) << "\n"
int anss = t.Query(1, 1, cb).first;
int ret = t.Query(1, 1, cb).second;
r[n + 10] = h, c[n + 10] = w;
ans.push_back(n + 10);
while(ret){
ans.push_back(ret);
ret = pre[ret];
}
r[n + 20] = 1, c[n + 20] = 1;
ans.push_back(n + 20);
reverse(ans.begin(), ans.end());
// for(auto i : ans) cout << i << " ";
// cout << "\n";
cout << anss << "\n";
for(int i = 1; i < ans.size(); i++){
int id = ans[i], lst = ans[i - 1];
int x = abs(r[id] - r[lst]), y = abs(c[id] - c[lst]);
// cout << x << "+" << y << "\n";
for(int j = 1; j <= x; j++) cout << "D";
for(int j = 1; j <= y; j++) cout << "R";
}
cout << "\n";
}

G

题意

俩人在玩游戏, 对象是一棵树, 根为 1

其中一个人 A 在树上指定了 k 个节点, 另一个人 B 要从 1 出发, 经过这些点回到 1

A 想最大化路径长度, B 想最小化, 求对于 k=1,2,...,n , 最优策略下的路径长度

解答

披着博弈外皮的贪心

如果告诉你这 k 个点, 那么 B 的最优策略就是这 k 个点到根的路径并的长度 2这个显然, 或者手算一下就可以得出

如果这 k 个点是依次加入集合的, 那么每次的增量就是加入点到根的路径上、之前没被走过的边的边权和 (2)

对于 A, 他的策略就是选一个当前情况(有一些边被走过, 不重复计入)下距离 1 最远的点, 加入集合

所以只需要实现一个数据结构, 支持:

  1. 查询到 1 的最大距离
  2. 点到根的路径上边权置零

性质: 路径都是向根且连续的!并且每条边只会修改一次

求一下 Dfn 然后线段树维护即可

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 10, MOD = 998244353;
int n;
int u, v, w;
int dfn[N], cdfn, fa[N][2], dis[N], si[N]; // fa : father, edge_val
int tag[N];
struct Edge{
int v, w;
};
vector <Edge> e[N];
struct Segment_Tree{
struct Node{
int l, r;
int maxi, maxid, tag;
}t[N];
struct Ret{
int maxi, maxid;
}ret;
void Update(int id, int x){
t[id].maxi += x;
t[id].tag += x;
}
void Push_down(int id){
Update(id * 2, t[id].tag);
Update(id * 2 + 1, t[id].tag);
t[id].tag = 0;
}
void Push_up(int id){
if(t[id * 2].maxi > t[id * 2 + 1].maxi){
t[id].maxi = t[id * 2].maxi;
t[id].maxid = t[id * 2].maxid;
}else{
t[id].maxi = t[id * 2 + 1].maxi;
t[id].maxid = t[id * 2 + 1].maxid;
}
}
void Build(int id, int l, int r){
t[id].l = l, t[id].r = r;
if(l == r){
t[id].maxi = dis[l];
t[id].maxid = l;
return;
}
int mid = (l + r) / 2;
Build(id * 2, l, mid);
Build(id * 2 + 1, mid + 1, r);
Push_up(id);
}
void Modify(int id, int l, int r, int x){
if(r < t[id].l || t[id].r < l){
return;
}
if(l <= t[id].l && t[id].r <= r){
Update(id, x);
return;
}
Push_down(id);
Modify(id * 2, l, r, x);
Modify(id * 2 + 1, l, r, x);
Push_up(id);
}
Ret Query(int id, int l, int r){
if(r < t[id].l || t[id].r < l){
return {0, 0};
}
if(l <= t[id].l && t[id].r <= r){
return {t[id].maxi, t[id].maxid};
}
Push_down(id);
Ret a = Query(id * 2, l, r);
Ret b = Query(id * 2 + 1, l, r);
a = (a.maxi > b.maxi) ? a : b;
return a;
}
}tr;
void Get_Dfn(int x, int y, int w){
dfn[x] = ++cdfn;
si[dfn[x]] = 1;
dis[dfn[x]] = dis[dfn[y]] + w;
fa[dfn[x]][0] = dfn[y], fa[dfn[x]][1] = w;
for(auto i : e[x]){
if(i.v == y) continue;
Get_Dfn(i.v, x, i.w);
si[dfn[x]] += si[dfn[i.v]];
}
}
struct Delete{
int idx, val;
};
vector <Delete> del;
void Del(int x){
int p = x;
while(!tag[p]){
tag[p] = 1;
tr.Modify(1, p, p + si[p] - 1, -fa[p][1]);
p = fa[p][0];
}
}
signed main(){
// freopen("1.in", "r", stdin);
cin >> n;
for(int i = 1; i < n; i++){
cin >> u >> v >> w;
e[u].push_back({v, w});
e[v].push_back({u, w});
}
Get_Dfn(1, 0, 0);
tr.Build(1, 1, n);
tag[1] = 1; // 终止点
int ans = 0;
for(int i = 1; i <= n; i++){
auto ret = tr.Query(1, 1, n);
// cout << ret.maxid << " " << ret.maxi << "\n";
ans += ret.maxi * 2;
cout << ans << "\n";
Del(ret.maxid);
}
}
posted on   Bubble_e  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话



点击右上角即可分享
微信分享提示