[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

要求操作后 \(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;
}
posted @ 2020-07-27 01:01  *Clouder  阅读(272)  评论(0编辑  收藏  举报