[Atcoder]M-Solutions 题解
Before the Beginning
转载请将本段放在文章开头显眼处,如有二次创作请标明。
原文链接:https://www.codein.icu/atcodermsolutions/
前言
这场感觉整体难度较低,场上ABCD基本都是签到题,EF的细节比较麻烦。
A - Kyu in AtCoder
按题意模拟即可。
#include <cstdio> #include <algorithm> #include <ctype.h> const int bufSize = 1e6; inline char nc() { #ifdef DEBUG return getchar(); #endif static char buf[bufSize], *p1 = buf, *p2 = buf; return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, bufSize, stdin), p1 == p2) ? EOF : *p1++; } inline void read(char *s) { static char c; for (; !isalpha(c); c = nc()); for (; isalpha(c); c = nc()) *s++ = c; *s = '\0'; } template<typename T> inline T read(T &r) { static char c; static int flag; flag = 1, r = 0; for (c = nc(); !isdigit(c); c = nc()) if (c == '-') flag = -1; for (; isdigit(c); c = nc()) r = r * 10 + c - 48; return r *= flag; } int x; int main() { read(x); if(x <= 599) puts("8"); else if(x <= 799) puts("7"); else if(x <= 999) puts("6"); else if(x <= 1199) puts("5"); else if(x <= 1399) puts("4"); else if(x <= 1599) puts("3"); else if(x <= 1799) puts("2"); else puts("1"); return 0; }
B - Magic 2
要求操作后 严格升序,那么先保证 偏序,再加 即可。
#include <cstdio> #include <algorithm> #include <ctype.h> const int bufSize = 1e6; inline char nc() { #ifdef DEBUG return getchar(); #endif static char buf[bufSize], *p1 = buf, *p2 = buf; return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, bufSize, stdin), p1 == p2) ? EOF : *p1++; } inline void read(char *s) { static char c; for (; !isalpha(c); c = nc()); for (; isalpha(c); c = nc()) *s++ = c; *s = '\0'; } template<typename T> inline T read(T &r) { static char c; static int flag; flag = 1, r = 0; for (c = nc(); !isdigit(c); c = nc()) if (c == '-') flag = -1; for (; isdigit(c); c = nc()) r = r * 10 + c - 48; return r *= flag; } int a,b,c,k; int main() { read(a),read(b),read(c),read(k); while(k && b <= a) --k,b<<=1; while(k && c <= b) --k,c<<=1; if(a < b && b < c) puts("Yes"); else puts("No"); return 0; }
C - Marks
定义分数函数 ,求对于每个 , 与 的大小关系。
显然化简完之后就是比较 与 的大小。
#include <cstdio> #include <algorithm> #include <ctype.h> const int bufSize = 1e6; inline char nc() { #ifdef DEBUG return getchar(); #endif static char buf[bufSize], *p1 = buf, *p2 = buf; return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, bufSize, stdin), p1 == p2) ? EOF : *p1++; } inline void read(char *s) { static char c; for (; !isalpha(c); c = nc()); for (; isalpha(c); c = nc()) *s++ = c; *s = '\0'; } template<typename T> inline T read(T &r) { static char c; static int flag; flag = 1, r = 0; for (c = nc(); !isdigit(c); c = nc()) if (c == '-') flag = -1; for (; isdigit(c); c = nc()) r = r * 10 + c - 48; return r *= flag; } const int maxn = 2e6 + 100; int n,k; int a[maxn]; int main() { read(n),read(k); for(int i = 1;i<=n;++i) read(a[i]); for(int i = k + 1;i<=n;++i) { if(a[i] > a[i-k]) puts("Yes"); else puts("No"); } return 0; }
D - Road to Millionaire
这道 的动态规划不知道为啥数据范围这么小,而且还可以用斜率优化做到 ,如果想到贪心更是线性的。
定义 为在 位置持有的最多钱数。
初始有 ,转移时,枚举 ,在 处买入, 处卖出,即有:
感觉可以卡到需要高精,不过数据没卡,不知道是不是题目描述保证了答案范围。
#include <cstdio> #include <algorithm> #include <ctype.h> const int bufSize = 1e6; inline char nc() { #ifdef DEBUG return getchar(); #endif static char buf[bufSize], *p1 = buf, *p2 = buf; return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, bufSize, stdin), p1 == p2) ? EOF : *p1++; } inline void read(char *s) { static char c; for (; !isalpha(c); c = nc()); for (; isalpha(c); c = nc()) *s++ = c; *s = '\0'; } template<typename T> inline T read(T &r) { static char c; static int flag; flag = 1, r = 0; for (c = nc(); !isdigit(c); c = nc()) if (c == '-') flag = -1; for (; isdigit(c); c = nc()) r = r * 10 + c - 48; return r *= flag; } #define int long long const int maxn = 1e5; int n; int a[maxn],f[maxn]; signed main() { read(n); for (int i = 1; i <= n; ++i) read(a[i]); f[1] = 1000; for(int i = 2;i<=n;++i) { f[i] = f[i-1]; for(int j = 1;j<i;++j) { if(a[i] <= a[j]) continue; int num = f[j] / a[j]; f[i] = std::max(f[i],f[j] - num * a[j] + num * a[i]); } } printf("%lld\n",f[n]); return 0; }
来点废话,用斜率优化做的话:
定义
对于决策点 与 ,若从 转移比 优,则有:
化简一下,得到:
若 ,则直接除去:
时, 比 优。
此时整理可得
用另一种大家比较熟悉的方式推到一遍:
DP方程可以写作:
移项得:
设 ,,,,即写作:
,求 最大即求纵截距最小。
维护下凸包,斜率单调递增,二分出 的第一个点。
记
稍微整理得到:
,即与上式相同。
然而此时遇到了问题,即点的横坐标并不单调,这个凸包的维护颇为麻烦。可以用平衡树维护凸包,或者使用 cdq 分治套斜率优化。
本蒟蒻不会平衡树,所以写了 cdq 分治套斜率优化,在获取答案时二分,复杂度是 的。
由于某些未知的细节错误,有几个点WA了,所以代码就不贴了。
还有一种贪心做法, 时,总在 买入, 卖出。
思考一下就不难发现是正确的,时间复杂度 。
E - M's Solution
首先需要几个大力的结论。
- 选择的直线一定落经过某个点。考虑直线将平面分为两半,一半中的点 和记为 ,另一半中点 和记为 。若 ,则直线在最近两点中间任意移动不影响贡献,否则靠近 更大那一方更优,即经过某点时。
- 两条直线不可能只经过一个点。一条直线经过某个点后,该点的贡献一定为 ,因此可以将该点视作删去,此时另一条直线根据第一条结论,选择经过其他点更优。
那么对于一个点有三种状态:无直线经过、横直线经过、纵直线经过。
直接暴力搜索点,复杂度有 ,考虑如何统计答案。
可以维护一个 为 点到最近直线的距离,每次加直线更新。
答案即为 ,时间复杂度为 。
那么总复杂度即为 ,可以通过本题。
#include <cstdio> #include <algorithm> #include <ctype.h> #define DEBUG const int bufSize = 1e6; inline char nc() { #ifdef DEBUG return getchar(); #endif static char buf[bufSize], *p1 = buf, *p2 = buf; return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, bufSize, stdin), p1 == p2) ? EOF : *p1++; } inline void read(char *s) { static char c; for (; !isalpha(c); c = nc()); for (; isalpha(c); c = nc()) *s++ = c; *s = '\0'; } template<typename T> inline T read(T &r) { static char c; static int flag; flag = 1, r = 0; for (c = nc(); !isdigit(c); c = nc()) if (c == '-') flag = -1; for (; isdigit(c); c = nc()) r = r * 10 + c - 48; return r *= flag; } const int maxn = 40; int n; struct node { int x,y,p; }A[maxn]; int mindis[maxn],steps; long long ans[maxn],tans; template<typename T> inline T _abs(const T &x){return x > 0 ? x : -x;} void dfs(int x) { if(steps > n) return; tans = 0; for (int i = 1; i <= n; ++i) tans += 1ll * A[i].p * mindis[i]; ans[steps] = std::min(ans[steps],tans); if(x > n) return; // printf("%d %d %d\n",steps,ans[steps],tans); dfs(x + 1); int copy[16]; for (int i = 1; i <= n; ++i) copy[i] = mindis[i]; ++steps; for (int i = 1; i <= n; ++i) mindis[i] = std::min(mindis[i],_abs(A[i].x - A[x].x)); dfs(x + 1); --steps; for (int i = 1; i <= n; ++i) mindis[i] = copy[i]; ++steps; for (int i = 1; i <= n; ++i) mindis[i] = std::min(mindis[i],_abs(A[i].y - A[x].y)); dfs(x + 1); --steps; for (int i = 1; i <= n; ++i) mindis[i] = copy[i]; } int main() { read(n); for (int i = 1; i <= n; ++i) read(A[i].x),read(A[i].y),read(A[i].p); for(int i = 1;i<=n;++i) mindis[i] = std::min(_abs(A[i].x),_abs(A[i].y)); for (int i = 0; i <= n; ++i) ans[i] = 1ll << 62; dfs(1); for(int i = 0;i<=n;++i) printf("%lld\n",ans[i]); return 0; }
当然,题解中还给出了一种状压DP的做法。
枚举状态 ,每位记录该点是否有直线经过。
随后对于每个 ,枚举经过的点哪几个是横线,进行转移。
可以预处理横线、竖线状态时某点的 。
时间复杂度我不会证,但跑得比搜索快。
#include <cstdio> #include <bitset> #include <algorithm> #include <ctype.h> const int bufSize = 1e6; #define DEBUG inline char nc() { #ifdef DEBUG return getchar(); #endif static char buf[bufSize], *p1 = buf, *p2 = buf; return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, bufSize, stdin), p1 == p2) ? EOF : *p1++; } inline void read(char *s) { static char c; for (; !isalpha(c); c = nc()); for (; isalpha(c); c = nc()) *s++ = c; *s = '\0'; } template<typename T> inline T read(T &r) { static char c; static int flag; flag = 1, r = 0; for (c = nc(); !isdigit(c); c = nc()) if (c == '-') flag = -1; for (; isdigit(c); c = nc()) r = r * 10 + c - 48; return r *= flag; } const int maxn = 19; int n; int X[maxn],Y[maxn],P[maxn]; long long xdis[maxn][maxn],ydis[maxn][maxn]; long long xval[maxn][1<<maxn],yval[maxn][1<<maxn]; long long ans[maxn]; int id[maxn]; template<typename T> inline T abs(const T &x) {return x > 0 ? x : -x;} int main() { read(n); for (int i = 0; i < n; ++i) read(X[i]), read(Y[i]), read(P[i]); for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j) xdis[i][j] = abs(X[i] - X[j]), ydis[i][j] = abs(Y[i] - Y[j]); for (int i = 0; i <= n; ++i) ans[i] = 1ll << 62; int maxx = (1 << n) - 1; for (int i = 0; i <= maxx; ++i) for (int j = 0; j < n; ++j) { xval[j][i] = abs(X[j]), yval[j][i] = abs(Y[j]); for (int k = 0; k < n; ++k) if (i & (1 << k)) xval[j][i] = std::min(xval[j][i], xdis[k][j]), yval[j][i] = std::min(yval[j][i], ydis[k][j]); xval[j][i] *= P[j],yval[j][i] *= P[j]; } for (int i = 0; i <= maxx; ++i) { int num = 0; for (int j = i; j > 0; j >>= 1) num += j & 1; for (int j = i; j >= 0; --j) { j &= i; long long tans = 0; for (int k = 0; k < n; ++k) if (!((1 << k) & i)) tans += std::min(xval[k][j],yval[k][(~j) & i]); ans[num] = std::min(ans[num], tans); } } for (int i = 0; i <= n; ++i) printf("%lld\n", ans[i]); return 0; }
F - Air Safety
一个飞机与另一个飞机撞上,有两种可能:
- 反向飞行相撞。
- 横竖飞行相撞。
反向飞行相撞很好维护,根据飞行方向将飞机分类,以横向为例。
将向右、向左的飞机按 排序后,枚举向右的飞机,得出离他最近的向左的飞机。
即第一个 使得 ,由于双数组都单调,显然可以线性解决。
像这样讨论横纵即可。
横竖飞行相撞比较麻烦,有两种情况:
- 斜率为 。
- 斜率为 。
斜率为 的向上、向左飞机,向下、向右飞机会相撞。
以经过每个飞机的斜率为 的直线在 轴上的交点为下标,然后用类似上面的方法处理即可。
代码又臭又长,大体思路就是这样了。
#include <cstdio> #include <algorithm> #include <vector> #include <ctype.h> const int bufSize = 1e6; using namespace std; inline char nc() { #ifdef DEBUG return getchar(); #endif static char buf[bufSize], *p1 = buf, *p2 = buf; return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, bufSize, stdin), p1 == p2) ? EOF : *p1++; } inline void read(char *s) { static char c; for (; !isalpha(c); c = nc()); for (; isalpha(c); c = nc()) *s++ = c; *s = '\0'; } template<typename T> inline T read(T &r) { static char c; static int flag; flag = 1, r = 0; for (c = nc(); !isdigit(c); c = nc()) if (c == '-') flag = -1; for (; isdigit(c); c = nc()) r = r * 10 + c - 48; return r *= flag; } const int maxn = 401000; const int maxx = 200000; int n; char s[10]; vector<int> hrY[maxn], hlY[maxn]; vector<int> vuX[maxn], vdX[maxn]; vector<int> ua[maxn], da[maxn],la[maxn],ra[maxn],um[maxn],dm[maxn],lm[maxn],rm[maxn]; int X[maxn], Y[maxn]; int ADD[maxn], MINUS[maxn]; signed main() { read(n); for (int i = 1; i <= n; ++i) { int x,y; read(x), read(y), read(s + 1); if (s[1] == 'U') vuX[x].push_back(y), um[x - y + maxx].push_back(x), ua[x + y].push_back(x); else if (s[1] == 'D') vdX[x].push_back(y), da[x + y].push_back(x), dm[x - y + maxx].push_back(x); else if (s[1] == 'L') hlY[y].push_back(x), la[x + y].push_back(x), lm[x - y + maxx].push_back(x); else if (s[1] == 'R') hrY[y].push_back(x), rm[x - y + maxx].push_back(x), ra[x + y].push_back(x); X[i] = x, Y[i] = y; MINUS[i] = x - y + maxx, ADD[i] = x + y; } int xnum, ynum, addnum, minusnum; std::sort(X + 1, X + 1 + n); std::sort(Y + 1, Y + 1 + n); std::sort(ADD + 1, ADD + 1 + n); std::sort(MINUS + 1, MINUS + 1 + n); xnum = std::unique(X + 1, X + 1 + n) - X - 1; ynum = std::unique(Y + 1, Y + 1 + n) - Y - 1; addnum = std::unique(ADD + 1, ADD + 1 + n) - ADD - 1; minusnum = std::unique(MINUS + 1, MINUS + 1 + n) - MINUS - 1; int tans = 1 << 30; for (int i = 1; i <= xnum; ++i) { int x = X[i]; std::sort(vuX[x].begin(), vuX[x].end()); std::sort(vdX[x].begin(), vdX[x].end()); for (vector<int>::iterator it = vuX[x].begin(), p = vdX[x].begin(); it != vuX[x].end(); ++it) { while(p != vdX[x].end() && *it > *p) ++p; if(p == vdX[x].end()) break; tans = std::min(tans,(*p - *it) * 5); } } for (int i = 1; i <= ynum; ++i) { int y = Y[i]; std::sort(hlY[y].begin(), hlY[y].end()); std::sort(hrY[y].begin(), hrY[y].end()); for (vector<int>::iterator it = hrY[y].begin(), p = hlY[y].begin(); it != hrY[y].end(); ++it) { while(p != hlY[y].end() && *it > *p) ++p; if(p == hlY[y].end()) break; tans = std::min(tans,(*p - *it) * 5); } } for (int i = 1; i <= addnum; ++i) { int pos = ADD[i]; std::sort(ua[pos].begin(), ua[pos].end()); std::sort(ra[pos].begin(), ra[pos].end()); for (vector<int>::iterator it = ra[pos].begin(),p = ua[pos].begin(); it != ra[pos].end(); ++it) { while(p != ua[pos].end() && *it > *p) ++p; if(p == ua[pos].end()) break; tans = std::min(tans,(*p - *it) * 10); } std::sort(da[pos].begin(),da[pos].end()); std::sort(la[pos].begin(),la[pos].end()); for (vector<int>::iterator it = da[pos].begin(),p = la[pos].begin(); it != da[pos].end(); ++it) { while(p != la[pos].end() && *it > *p) ++p; if(p == la[pos].end()) break; tans = std::min(tans,(*p - *it) * 10); } } for (int i = 1; i <= minusnum; ++i) { int pos = MINUS[i]; std::sort(um[pos].begin(), um[pos].end()); std::sort(lm[pos].begin(), lm[pos].end()); for (vector<int>::iterator it = um[pos].begin(), p = lm[pos].begin(); it != um[pos].end(); ++it) { while(p != lm[pos].end() && *it > *p) ++p; if(p == lm[pos].end()) break; tans = std::min(tans,(*p - *it) * 10); } std::sort(dm[pos].begin(), dm[pos].end()); std::sort(rm[pos].begin(), rm[pos].end()); for (vector<int>::iterator it = rm[pos].begin(), p = dm[pos].begin(); it != rm[pos].end(); ++it) { while(p != dm[pos].end() && *it > *p) ++p; if(p == dm[pos].end()) break; tans = std::min(tans,(*p - *it) * 10); } } if(tans == 1<<30) puts("SAFE"); else printf("%lld\n",tans); return 0; }
【推荐】博客园的心动:当一群程序员决定开源共建一个真诚相亲平台
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】Flutter适配HarmonyOS 5知识地图,实战解析+高频避坑指南
【推荐】开源 Linux 服务器运维管理面板 1Panel V2 版本正式发布
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 协程本质是函数加状态机——零基础深入浅出 C++20 协程
· 编码之道,道心破碎。
· 记一次 .NET 某发证机系统 崩溃分析
· 微服务架构学习与思考:SOA架构与微服务架构对比分析
· tomcat为什么假死了
· 知名开源项目Alist被收购!惹程序员众怒,开团炮轰甲方
· 突发,小红书开发者后门被破解?!
· 历时半年,我将一个大型asp.net的零代码快速开发平台转成了java
· Windows11 关闭搜索栏中的Web网页搜索
· [原创]《C#高级GDI+实战:从零开发一个流程图》第03章:画一个线,连接两个矩形!