zoukankan      html  css  js  c++  java
  • [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 = '';
    }
    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 = '';
    }
    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 = '';
    }
    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(nlog ^2 n)),如果想到贪心更是线性的。
    定义 (f(i)) 为在 (i) 位置持有的最多钱数。
    初始有 (f(1) = 1000),转移时,枚举 (j),在 (j) 处买入,(i) 处卖出,即有:
    (f(i) = max(f(j) + lfloor dfrac{f(j)}{a_j}) floor imes (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 = '';
    }
    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
    ",f[n]);
        return 0;
    }
    

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

    (num(j) - num(k) > 0),则直接除去:

    (dfrac{f(k) - f(j) + num(j) imes a_j - num(k) imes a_k}{num(j) - num(k)} > a_i) 时,(j)(k) 优。

    此时整理可得 (x = num(j),y = num(j) imes a_j - f(j),k = a_i)

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

    DP方程可以写作:(f(i) = f(j) - num(j) imes a_j + num(j) imes a_i)

    移项得:

    (a_i imes num(j) - f(i) = num(j) imes a_j - f(j))

    (k = a_i)(x = num(j))(b = -f(i))(y = num(j) imes a_j - f(j)),即写作:

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

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

    (j = pos,k = pos + 1)

    (dfrac{num(j) imes a_j - f(j) - num(k) imes a_k + f(k)}{num(j) - num(k)} > a_i)

    稍微整理得到:

    (dfrac{f(k) - f(j) + num(j) imes a_j - num(k) imes 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) imes 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 = '';
    }
    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
    ",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
    ",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 = '';
    }
    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
    ", 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 = '';
    }
    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
    ",tans);
        return 0;
    }
    
  • 相关阅读:
    高阶函数之函数柯里化function currying
    学习javascript设计模式之状态模式
    学习javascript设计模式之代理模式
    学习javascript设计模式之发布-订阅(观察者)模式
    Json序列化提示缺少编译器要求的成员“ystem.Runtime.CompilerServices.ExtensionAttribute..ctor”
    获取实例
    webservice跨域问题
    IIS客户端没有权限
    IIS7.5 错误代码0x8007007e HTTP 错误 500.19
    获取当前文件夹路径
  • 原文地址:https://www.cnblogs.com/Clouder-Blog/p/atcodermsolutions.html
Copyright © 2011-2022 走看看