[Atcoder]M-Solutions 题解
本文最后更新于 181 天前,其中的信息可能已经有所发展或是发生改变。

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

要求操作后 $A$ $B$ $C$ 严格升序,那么先保证 $A$ $B$ 偏序,再加 $C$ 即可。

#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

定义分数函数 $f(i) = \prod_{j = i – k + 1}^i A_j$,求对于每个 $i$,$f(i – 1)$ 与 $f(i)$ 的大小关系。

显然化简完之后就是比较 $A_{i – k}$ 与 $A_i$ 的大小。

#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

这道 $O(n^2)$ 的动态规划不知道为啥数据范围这么小,而且还可以用斜率优化做到 $O(n\log ^2 n)$,如果想到贪心更是线性的。
定义 $f(i)$ 为在 $i$ 位置持有的最多钱数。
初始有 $f(1) = 1000$,转移时,枚举 $j$,在 $j$ 处买入,$i$ 处卖出,即有:
$f(i) = \max(f(j) + \lfloor \dfrac{f(j)}{a_j}) \rfloor \times (a_i – a_j))$
感觉可以卡到需要高精,不过数据没卡,不知道是不是题目描述保证了答案范围。

#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;
}

来点废话,用斜率优化做的话:
定义 $num(j) = \dfrac{f(j)}{a_j}$
对于决策点 $j$ 与 $k$,若从 $j$ 转移比 $k$ 优,则有:
$f(j) – num(j) \times a_j + num(j) \times a_i > f(k) – num(k) \times a_k + num(k) \times a_i$
化简一下,得到:
$f(k) – f(j) + num(j) \times a_j – num(k) \times a_k > [num(j) – num(k)] \times a_i$

若 $num(j) – num(k) > 0$,则直接除去:

$\dfrac{f(k) – f(j) + num(j) \times a_j – num(k) \times a_k}{num(j) – num(k)} > a_i$ 时,$j$ 比 $k$ 优。

此时整理可得 $x = num(j),y = num(j) \times a_j – f(j),k = a_i$

用另一种大家比较熟悉的方式推到一遍:

DP方程可以写作:$f(i) = f(j) – num(j) \times a_j + num(j) \times a_i$

移项得:

$a_i \times num(j) – f(i) = num(j) \times a_j – f(j)$

设 $k = a_i$,$x = num(j)$,$b = -f(i)$,$y = num(j) \times a_j – f(j)$,即写作:

$k\times x + b = y$,求 $f(i)$ 最大即求纵截距最小。

维护下凸包,斜率单调递增,二分出 $k(pos,pos + 1) > a_i$ 的第一个点。

记 $j = pos,k = pos + 1$

$\dfrac{num(j) \times a_j – f(j) – num(k) \times a_k + f(k)}{num(j) – num(k)} > a_i$

稍微整理得到:

$\dfrac{f(k) – f(j) + num(j) \times a_j – num(k) \times a_k)}{num(j) – num(k)} > a_i$,即与上式相同。

然而此时遇到了问题,即点的横坐标并不单调,这个凸包的维护颇为麻烦。可以用平衡树维护凸包,或者使用 cdq 分治套斜率优化。

本蒟蒻不会平衡树,所以写了 cdq 分治套斜率优化,在获取答案时二分,复杂度是 $o(n \log ^2 n)$ 的。

由于某些未知的细节错误,有几个点WA了,所以代码就不贴了。

还有一种贪心做法,$A_i < A_{i+1}$ 时,总在 $A_i$ 买入,$A_{i+1}$ 卖出。

思考一下就不难发现是正确的,时间复杂度 $O(n)$。

E – M’s Solution

首先需要几个大力的结论。

  1. 选择的直线一定落经过某个点。考虑直线将平面分为两半,一半中的点 $P$ 和记为 $P_1$,另一半中点 $P$ 和记为 $P_2$。若 $P_1 = P_2$,则直线在最近两点中间任意移动不影响贡献,否则靠近 $P$ 更大那一方更优,即经过某点时。
  2. 两条直线不可能只经过一个点。一条直线经过某个点后,该点的贡献一定为 $0$,因此可以将该点视作删去,此时另一条直线根据第一条结论,选择经过其他点更优。

那么对于一个点有三种状态:无直线经过、横直线经过、纵直线经过。

直接暴力搜索点,复杂度有 $O(3^n)$,考虑如何统计答案。

可以维护一个 $mindis(i)$ 为 $i$ 点到最近直线的距离,每次加直线更新。

答案即为 $\sum mindis(i) \times P_i$,时间复杂度为 $O(n)$。

那么总复杂度即为 $O(n3^n)$,可以通过本题。

#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的做法。
枚举状态 $status$,每位记录该点是否有直线经过。
随后对于每个 $status$,枚举经过的点哪几个是横线,进行转移。
可以预处理横线、竖线状态时某点的 $mindis$。
时间复杂度我不会证,但跑得比搜索快。

#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

一个飞机与另一个飞机撞上,有两种可能:

  1. 反向飞行相撞。
  2. 横竖飞行相撞。

反向飞行相撞很好维护,根据飞行方向将飞机分类,以横向为例。
将向右、向左的飞机按 $x$ 排序后,枚举向右的飞机,得出离他最近的向左的飞机。
即第一个 $j$ 使得 $x_i \leq x_j$,由于双数组都单调,显然可以线性解决。
像这样讨论横纵即可。

横竖飞行相撞比较麻烦,有两种情况:

  1. 斜率为 $1$。
  2. 斜率为 $-1$。

斜率为 $1$ 的向上、向左飞机,向下、向右飞机会相撞。
以经过每个飞机的斜率为 $1$ 的直线在 $x$ 轴上的交点为下标,然后用类似上面的方法处理即可。
代码又臭又长,大体思路就是这样了。

#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;
}
本文标题:[Atcoder]M-Solutions 题解
本文作者:Clouder
本文链接: https://www.codein.icu/atcodermsolutions/
转载请标明。

评论

  1. 6月前
    2020-7-27 11:56:27

    太强啦,您又ak了

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇