0118ACM自闭赛

Posted zqytcl

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了0118ACM自闭赛相关的知识,希望对你有一定的参考价值。

<前言>

ACM赛:vjudge 20200118acm赛

好好记录一下自己干了啥。

就是在颓废。

<正文>

<考试经历>

今天acm赛队友随便找了个,结果就是个懒货,题都不写几道,哎。

他写了一道最水的E,然后两道是我写的,我挺想骂人。

这次的题简直毒瘤,无法描述。

比赛地址

密码:nicaibudao


上场我先说队友(zjh)分配一下题。他就直接捡了个E题,几分钟爆切,全场一血,我当时吓死了,然而考试后得知E题最水,代码就几行。

我拿到了D题,稍微看了下,做过原题或是一样的题,几分钟也写完了,却不是一血。区间最小值和不就是维护一个单调栈然后瞎搞就行了。来回各一趟维护出数组,再直接计算。

接着全场陷入瓶颈期,很长时间没人切出除了DE外的题。

然后先不管fjz的吊打全场,我开始磕F题,队友写完E之后就开始无(da)所(shui)事(te)事(shui),他们都去吃饭了,我在机房磕。

一直在6点WA,不知为何。万念俱灰间加了个long long 他还就真过了。。(掀桌)

然后时间就是各题看看,也磕不出来惹。。。自闭

最后只有rank5,没泡面,但是我两顿没吃了啊啊啊啊啊啊啊啊啊啊啊啊啊啊。

晚上,也就是现在,开始四处觅食,感觉都没吃的,就自闭写blog去了。

游戏体验极差,全程被吊打。也就一开始的时候成为了好久的rank1,因为大家都做不出来。

贴一张骄傲的图(PS:号是队友的,取名雨我无瓜):
技术图片


部分题解

A - Negative Cycle AtCoder - 5147

A题不会,抄了一下,至少自己写不出来,看着还好理解的样子。

(mathrm{Code:})

#include <bits/stdc++.h>
#define N      510
#define int    long long
using namespace std;
int a[N][N] = {};
int read()
{
    int  s = 0, w = 1;
    char c = getchar();
    while ((c < '0' || c > '9') && c != '-')
        c = getchar();
    if (c == '-')
        c = getchar(), w = -1;
    while (c <= '9' && c >= '0')
        s = (s << 3) + (s << 1) + c - '0', c = getchar();
    return s * w;
}
int n;
int b[N][N] = {}, c[N][N] = {};
int f[N][N] = {};
signed main()
{
    n = read();
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= n; ++j)
            if (i != j)
                a[i][j] = read();
    for (int i = 1; i <= n; ++i)
        for (int j = n; j >= i + 1; --j)
        {
            b[i][j] = b[i][j + 1];
            for (int k = 1; k <= i; ++k)
                b[i][j] += a[j][k];
        }
    for (int i = 1; i <= n; ++i)
        for (int j = i + 1; j <= n + 1; ++j)
        {
            c[i][j] = c[i][j - 1];
            for (int k = i; k <= j - 1; ++k)
                c[i][j] += a[k][j];
        }
    memset(f, 0x3f, sizeof(f));
    f[0][0] = 0;
    for (int i = 0; i <= n; ++i)
        for (int j = i; j <= n; ++j)
            if (f[i][j] < 1e18)
                for (int k = j + 1; k <= n + 1; ++k)
                    f[j][k] = min(f[j][k], f[i][j] + c[j + 1][k] + b[j][k + 1] - b[i][k + 1]);
    int minn = 1e18;
    for (int i = 0; i <= n; ++i)
        minn = min(minn, f[i][n + 1]);
    printf("%lld
", minn);
    return 0;
}

B - Robots and Exits - AtCoder - 4353

B题干脆不写了,刷题记录表里随便写点就行了。

C - 和風いろはちゃん / Iroha and Haiku - AtCoder - 1975

C题同。

D - Minimum Sum - AtCoder - 2060

稍微讲一下D,这题就是求每段子区间的最小值和。

每段考虑并不好做,考虑计算每个值的贡献。

一个位于(i)处的值(x)能影响到的区间一定是以它为最小值的区间,我们只需找到左右第一个比它小的值位置,这两个位置之间的区间都是以(x)为最小值得区间,计算个数以及贡献即可。

[ egin{aligned} & sum_{1leq l leq r leq n} min(a_l,a_{l+1}……a_{r-1},a_r) &= sum_{min(a_l,a_{l+1}……a_{r-1},a_r)=x} (r-i+1) imes (i-l+1) imes x end{aligned} ]

两个单调栈一开,维护左/右比x小的第一个数位置,就分别是l、r了。

(mathrm{Code:})

#include <bits/stdc++.h>
#define N      200010
#define int    long long
using namespace std;
int n;
int a[N] = {};
int read()
{
    int  s = 0, w = 1;
    char c = getchar();
    while ((c < '0' || c > '9') && c != '-')
        c = getchar();
    if (c == '-')
        w = -1, c = getchar();
    while (c <= '9' && c >= '0')
        s = (s << 3) + (s << 1) + c - '0', c = getchar();
    return s * w;
}
int pre[N] = {}, suf[N] = {};
int q[N << 1] = {}, h = 1, t = 0;
void push1(int x)
{
    while (h <= t && a[q[t]] >= a[x])
        suf[q[t]] = x - 1, --t;
    q[++t] = x;
}
void push2(int x)
{
    while (h <= t && a[q[t]] >= a[x])
        pre[q[t]] = x + 1, --t;
    q[++t] = x;
}
signed main()
{
    n = read();
    for (int i = 1; i <= n; ++i)
        a[i] = read(), pre[i] = 1, suf[i] = n;
    for (int i = 1; i <= n; ++i)
        push1(i);
    h = 1; t = 0;
    for (int i = n; i >= 1; --i)
        push2(i);
    int sum = 0;
    for (int i = 1; i <= n; ++i)
    {
        sum += (i - pre[i] + 1) * (suf[i] - i + 1) * a[i];
    }
    printf("%lld
", sum);
    return 0;
}

挺水一题,不知为何被拖了很久,没拿到一血。

E - STring - AtCoder - 2059

题目给一个巨tm大的操作次数((10^{10000}))就是告诉你随便删就好了,管够。

所以E作为本场最水被过了。

zjh的(mathrm{Code:})

#include <bits/stdc++.h>
using namespace std ;
inline int read ()
{
    char c = getchar () ; int x = 0 , f = 1 ;
    while (c < '0' || c > '9') { if (c == '-') f = -1 ; c = getchar () ; }
    while (c >= '0' && c <= '9'){ x = x * 10 + c - '0' ; c = getchar () ; }
    return x * f ;
}
string s ;
int st[200005] , ans , top ;
int main ()
{
    cin >> s ;
    ans = s.size () ;
    for (int i = 0 ; i < s.size () ; i ++)
    {
        if (s[i] == 'S') st[++ top] = 'S' ;
        else if (top) top -- , ans -= 2 ;
    }
    cout << ans ;
}

F - Pizza Delivery - Gym - 101986F

这题也是我写的。

一直被long long 卡,要是开了long long 我午饭时间后的第二发就可以A,能吃上一顿饭呢。(但是晚上吃到泡面还是挺赚的)

之前我的分类讨论中,一开始我就分了最短路边和非最短路边,两者又进行了细分,后来发现不必要,对于非最短路边,直接判断就好。

最短路边略麻烦。

你要确定删去之后会不会对最短路径有影响,就要判断该边是否是属于最短路的边(注意最短路可能有多条)组成的图的割边了。

若不是桥,说明会有其他路径代替本路径,取反这条边没啥卵用。

若是桥,则它取反之后无法找到更短或相等的路径代替,是可以证明的,但是我不会。

那么局势变得明朗起来:

  • 找最短路,标记所有最短路上的边。
  • 在最短路径组成的图中找割边,可用两种方式实现。

对于最短路边,若是割边则翻转会产生影响。不是割边则不会。

对于非最短路边,强行把它放在最短路上看看和原最短路比起来怎么样。

割边的两种查找方式:重新建图tarjan,最短路路径计数。

贴出计数法判割边的代码:

(mathrm{Code:})

#include <bits/stdc++.h>
#define N      200010
#define int    long long
#define inf    0x3f3f3f3f
#define mp(x, y)    make_pair(x, y)
using namespace std;
int n, m;
struct gragh
{
    int to[N] = {}, net[N] = {}, w[N] = {};
    int from[N] = {};
    int fl[N] = {}, len = 0;
    inline void inc(int x, int y, int z)
    {
        to[++len] = y;
        from[len] = x;
        w[len]    = z;
        net[len]  = fl[x];
        fl[x]     = len;
    }
} G[2];
struct tree
{
    int to[N << 1] = {}, net[N] = {}, w[N] = {};
    int fl[N] = {}, len = 0;
    inline void inc(int x, int y, int z)
    {
        to[++len] = y;
        w[len]    = z;
        net[len]  = fl[x];
        fl[x]     = len;
    }
} T;
int read()
{
    int  s = 0, w = 1;
    char c = getchar();
    while ((c < '0' || c > '9') && c != '-')
        c = getchar();
    if (c == '-')
        c = getchar(), w = -1;
    while (c <= '9' && c >= '0')
        s = (s << 3) + (s << 1) + c - '0', c = getchar();
    return s * w;
}
int vis[N] = {}, dis[2][N] = {};
int is[2][N] = {}, si[2][N] = {};
void DJ(int st, int p)
{
    memset(dis[p], 0x3f, sizeof(dis[p]));
    memset(vis, 0, sizeof(vis));
    memset(is[p], 0, sizeof(is[p]));
    dis[p][st] = 0;
    si[p][st]  = 1;
    priority_queue<pair<int, int> >q;
    q.push(mp(0, st));
    while (!q.empty())
    {
        int t = q.top().second;
        q.pop();
        if (vis[t])
            continue;
        vis[t] = 1;
        for (int i = G[p].fl[t]; i; i = G[p].net[i])
        {
            int v = G[p].to[i];
            if (dis[p][v] > dis[p][t] + G[p].w[i])
            {
                dis[p][v] = dis[p][t] + G[p].w[i];
                si[p][v]  = si[p][t];
                q.push(mp(-dis[p][v], v));
            }
            else if (dis[p][v] == dis[p][t] + G[p].w[i])
                si[p][v] += si[p][t], si[p][v] %= inf;
        }
    }
}
signed main()
{
    n = read();
    m = read();
    for (int i = 1; i <= m; ++i)
    {
        int x = read(), y = read(), z = read();
        G[0].inc(x, y, z);
        G[1].inc(y, x, z);
    }
    DJ(1, 0);
    DJ(2, 1);
    int vis[N] = {};
    for (int i = 1; i <= G[0].len; ++i)
        if (dis[0][G[0].from[i]] + G[0].w[i] + dis[1][G[0].to[i]] == dis[0][2])
            is[0][i] = 1, 
            vis[G[0].from[i]] = vis[G[0].to[i]] = 1;
    for (int i = 1; i <= G[0].len; ++i)
    {
        if (is[0][i])
        {
            int u = G[0].from[i], v = G[0].to[i];
            if (si[0][u] * si[1][v] % inf == si[0][2])
            {
                puts("SAD");
            }
            else
            {
                puts("SOSO");
            }
        }
        else
        {
            int u = G[0].from[i], v = G[0].to[i];
            int minn = dis[0][v] + G[0].w[i] + dis[1][u];
            if (minn < dis[0][2])
                puts("HAPPY");
            else
                puts("SOSO");
        }
    }
    return 0;
}

G - New Year and Old Subsequence - CodeForces - 750E

毒瘤题。线段树优化广义矩乘,其实就是加速dp的递推。

我们将几种状态进行编号:(emptyset,2,20,201,2017)分别编号01234.

这样对于每一个递推,就有一种编号间的递推转移了。

重定义乘法为dp的递推,在本题中为加法变取min,乘法变加法。

考虑构造转移矩阵。

对于当前字符为‘2‘时:若要保持空集,则需删去,代价为1,若不变成空集,则可以不花费代价。其他每个状态保持都无需花费,故转移为:

[ mathrm{ egin{bmatrix} 0&1&inf&inf&infinf&0&inf&inf&infinf&inf&0&inf&infinf&inf&inf&0&infinf&inf&inf&inf&0 end{bmatrix} } ]
其他字符类似。

对于字符‘6‘,因为无论如何不能要它,所以从(201、2017)的转移保持代价为1,因为题目为子序列,所以2017这种状态也不能加6

[ egin{bmatrix} 0&inf&inf&inf&infinf&0&inf&inf&infinf&inf&0&inf&infinf&inf&inf&1&infinf&inf&inf&inf&1 end{bmatrix} ]

用线段树维护运算,注意左右子矩阵顺序,即使这题中没问题,但是其他的广义运算是要注意的。

代码:

(mathrm{Code:})

#include <bits/stdc++.h>
#define int    long long
#define N      200010
using namespace std;
int n, m;
struct matrix
{
    int a[5][5];
    int n, m;
    matrix()
    {
        n = m = 0;
        memset(a, 0x3f, sizeof(a));
    }
    inline int *operator [](int x)
    {
        return a[x];
    }
    matrix operator *(matrix b)
    {
        matrix c;
        c.n = n; c.m = b.m;
        for (int i = 0; i < c.n; ++i)
            for (int j = 0; j < c.m; ++j)
                for (int k = 0; k < m; ++k)
                    c.a[i][j] = min(c.a[i][j], a[i][k] + b.a[k][j]);
        return c;
    }
};
struct segment_tree
{
    int    l, r;
    matrix k;
} tr[N << 2];
int read()
{
    int  s = 0, w = 1;
    char c = getchar();
    while ((c < '0' || c > '9') && c != '-')
        c = getchar();
    if (c == '-')
        w = -1, c = getchar();
    while (c <= '9' && c >= '0')
        s = (s << 3) + (s << 1) + c - '0', c = getchar();
    return s * w;
}
char s[N] = {};
inline void push(int p)
{
    tr[p].k = tr[p << 1].k * tr[p << 1 | 1].k;
}
void build(int p, int l, int r)
{
    tr[p].l = l; tr[p].r = r;
    if (l == r)
    {
        tr[p].k.n = tr[p].k.m = 5;
        for (int i = 0; i < 5; ++i)
            tr[p].k[i][i] = 0;
        if (s[l] == '2')
            tr[p].k[0][0] = 1, tr[p].k[0][1] = 0;
        if (s[l] == '0')
            tr[p].k[1][1] = 1, tr[p].k[1][2] = 0;
        if (s[l] == '1')
            tr[p].k[2][2] = 1, tr[p].k[2][3] = 0;
        if (s[l] == '7')
            tr[p].k[3][3] = 1, tr[p].k[3][4] = 0;
        if (s[l] == '6')
            tr[p].k[3][3] = 1, tr[p].k[4][4] = 1;
        return;
    }
    int mid = (l + r) >> 1;
    build(p << 1, l, mid);
    build(p << 1 | 1, mid + 1, r);
    push(p);
}
matrix ask(int p, int l, int r)
{
    if (l <= tr[p].l && tr[p].r <= r)
        return tr[p].k;
    int mid = (tr[p].l + tr[p].r) >> 1;
    if (r <= mid)
        return ask(p << 1, l, r);
    if (l > mid)
        return ask(p << 1 | 1, l, r);
    return ask(p << 1, l, r) * ask(p << 1 | 1, l, r);
}
signed main()
{
    n = read();
    m = read();
    cin >> (s + 1);
    build(1, 1, n);
    for (int i = 1; i <= m; ++i)
    {
        int    x = read(), y = read();
        matrix ans = ask(1, x, y);
        printf("%lld
", ans[0][4] > n ? -1 : ans[0][4]);
    }
    return 0;
}

H - Shorten Diameter - AtCoder - 1981

没搞,听说第二水,也不想写了。

<后记>

然后就是这样了,一场ACM打完,神清气爽,回头刷题。

以上是关于0118ACM自闭赛的主要内容,如果未能解决你的问题,请参考以下文章

10.20字符串自闭赛

ZROI 2019 寒假省选线下自闭赛2

9.10的evening自闭信心赛

ACM新手赛体会

ACM团队招新赛题解

2018 ACM 国际大学生程序设计竞赛上海大都会赛