ABC 369
刚才翻上次写的 abc 366 题解, 发现语言挺抽象, 导致自己都快看不懂了, 这回写好点
这段时间第一次 Rated, 情况一般吧, F 忘给同一个 \(x\) 的所有 \(y\) 排序了, 今天 (9.1) 早上突然看出来了。G 没有细看, 以为是个博弈论, 现在才发现是个简单贪心
369 这数挺吉利哈哈, 济南好像有说法是初三初六初九宜出行, 济南公交也有个 app 叫 369。这次就当作重新学 oi 的开始了, 369, 宜踏上新的征程!
A
题意
给定 \(a, b\), 求一个 \(x\), 使得三个数满足 \(q - p = r - q\) 的形式(\(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)\) 的个数, 使得 \(a_l...a_r\) 是等差数列(\(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!\) 种, 再枚举每条路从 \(u\) 到 \(v\) 还是反过来, \(2^5\) 种, 发现完全可过
然后就完事了, 最短路 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\) 的方式输出一种行走方案
网格图的大小是 \(2e5 * 2e5\) 的, 总共 \(2e5\) 个标记点
解答
如果格子小一点, 那么就是个普及组题。既然这样, 那么我们的算法肯定是基于标记点个数而非整个网格的。
考虑 ”传统“ 的转移:
\[dp(i, j) = max\{dp(ii, jj)\} + 1 \]\[(ii, jj) 在 (i, j) 的左上方 \]所以想起了二维问题的常见处理思路:固定一维扫另一维
从上到下扫过 \(x\) 轴, 枚举当前 \(x\) 的对应的所有 \(y\) (这里一定要排序!!!, 我因此 Wa 了两发)。
对于确定的 \(y\) , 求出 \(y' \le y\) 的 \(y'\) 对应的最大 \(dp\) 值, 因为固定了 \(x\) 的顺序是从上到下, 所以每一个 \(y'\) 内存储的信息都来自于小于当前 \(x\) 的 \(x'\), 开个线段树维护一下即可
代码
#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 的最大距离
- 点到根的路径上边权置零
性质: 路径都是向根且连续的!并且每条边只会修改一次
求一下 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);
}
}
标签:ABC,int,void,long,maxi,369,dfn,id
From: https://www.cnblogs.com/Bubblee/p/18391033