我不发个 Trash 食用感言我就是死了么

副标题:

舟舟都有的舟考但可能不是舟舟都有的舟赛总结(R14)

T1 modify

不简单的问题,首先得观察到序列权值和不变,然后结合「数列极差不超过 $1$」想到最终的每个元素权值在序列平均值附近。手玩一下就可以发现是 $\left\lfloor\dfrac{\sum\limits_{i = 1}^{n}{a_{i}}}{n}\right\rfloor$ 和 $\left\lceil\dfrac{\sum\limits_{i = 1}^{n}{a_{i}}}{n}\right\rceil$,光是这两点@liuzimin 同学就没有想到,所以这是个不简单的问题。

Code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n, sum, ave, r, ans, a[200001];
int main() {
//  freopen("modify.in", "r", stdin);
//  freopen("modify.out", "w", stdout);
    scanf("%lld", &n);
    for(int i = 1; i <= n; ++i) {
        scanf("%lld", &a[i]);
        sum += a[i];
    }
    ave = sum / n;
    r = sum - ave * n;
    stable_sort(a + 1, a + 1 + n);
    for(int i = n; i >= 1; --i) {
        ans += abs(a[i] - ave - (bool)r);
        r -= (bool)r;
    }
    printf("%lld", ans >> 1);
    return 0;
}

T2 knuth

不简单的问题。看到「这种表示法的主要特点是数字的分组是指数的,而不是线性的。也就是说,每个计数的新单位的位数都会增加一倍。」大概猜到此题与二进制相关,观察题目后发现题目本质是一个 $\texttt{A + B Problem}$,难点在于输入输出。

容易发现每一个计数词对应的状态数正好是二的整数次幂,然后再看一下提示,猜测每一个计数次对应一个二进制位,然后用 map 处理一下就是简单的 $\texttt{A + B Problem}$ 了。

为什么是不简单的问题?因为 get_line 不会过滤 \r,不少同学因此挂分,所以不简单。

Code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int a, b, c;
map<string, int> mp;
map<int, string> mmp; 
string s, str;
int main() {
//  freopen("knuth.in", "r", stdin);
//  freopen("knuth.out", "w", stdout);
    mp["one"] = 0;
    mp["ten"] = 1 << 0;
    mp["hundred"] = 1 << 1;
    mp["myriad"] = 1 << 2;
    mp["myllion"] = 1 << 3;
    mp["byllion"] = 1 << 4;
    mp["tryllion"] = 1 << 5;
    mp["quadryllion"] = 1 << 6;
    mp["quintyllion"] = 1 << 7;
    mp["sextyllion"] = 1 << 8;
    mp["septyllion"] = 1 << 9;
    mp["octyllion"] = 1 << 10;
    mp["nonyllion"] = 1 << 11;
    mp["decyllion"] = 1 << 12;
    for(const auto& i : mp) mmp[i.second] = i.first;
    getline(cin, s);
    stringstream ss(s);
    while(ss >> str) a += mp[str];
    getline(cin, s);
    stringstream sss(s);
    while(sss >> str) b += mp[str];
    c = a + b;
    if(!(c & 1)) cout << "one ";
    for(int i = 0; i <= 12; ++i) {
        if((c >> i) & 1) cout << mmp[1 << i] << " "; 
    }
    return 0;
}

T3 tower

不简单的问题。考场乱打dp直接走人,然鹅数据弱到可以骗70pts。

如果按题意模拟,本质是搜索,但是时间复杂度太大。

可以发现对于 $a_{i} = -1$ 的情况一次加入的结点是一段连续的区间,可以使用线段树维护,优化建图。

因为之前我没做过线段树优化建图的题目,所以是不简单的问题。

Code:

#include <bits/stdc++.h>
namespace VividCycle {
    #include <bits/stdc++.h>
    #define getchar gc
    #define putchar pc
    #define fu(i, l, r) for(int i = l; i <= r; ++i)
    #define fd(i, l, r) for(int i = l; i >= r; --i)
    #define fo(i, v) for(const auto& i : v)
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    static char buffer[1 << 20 | 1]; static int outp = -1;
    void flush() {fwrite(buffer, 1, outp + 1, stdout), outp = -1;}
    void pc(const char& ch) {if(outp == (1 << 20)) flush(); buffer[++outp] = ch;}
    char gc() {static char ibuf[1 << 20], *p1 = ibuf, *p2 = ibuf; return p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, 1000010, stdin), p1 == p2) ? -1 : *p1++;}
    template<typename T> void read(T& x) {cin >> x; return; x = 0; int f = 1; char ch = getchar(); while(ch < '0' || ch > '9') f *= ((ch == '-') ? -1 : 1), ch = getchar(); while(ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar(); x *= f;}
    template<typename T, typename ...Args> void read(T& x, Args& ...args) {read(x), read(args...);}
    void write(const char& ch) {putchar(ch);} void write(const char s[]) {int len = strlen(s) - 1; fu(i, 0, len) putchar(s[i]);} void write(char s[]) {int len = strlen(s) - 1; fu(i, 0, len) putchar(s[i]);}
    template<typename T> void write(T x) {static char obuf[1 << 8]; static int p; p = 0; if(x < 0) x *= (putchar('-'), -1); if(!x) {putchar('0'); return;} while(x) obuf[++p] = x % 10 ^ 48, x /= 10; while(p) putchar(obuf[p--]);}
    template<typename T, typename ...Args> void write(T x, Args ...args) {write(x), write(args...);}
}
using namespace VividCycle;
int n, k, now, L, R, a[500002];
queue<int> q;
bool flag[500002];
struct Segment_Tree {
    int l[2000001], r[2000001];
    bool val[2000001];
    #define lc (k << 1)
    #define rc ((k << 1) | 1)
    #define mid ((l[k] + r[k]) >> 1)
    void push_up(int k) {
        val[k] = val[lc] | val[rc];
    }
    void build(int k) {
        val[k] = true;
        if(l[k] == r[k]) return;
        l[lc] = l[k], r[lc] = mid, l[rc] = mid + 1, r[rc] = r[k];
        build(lc), build(rc);
    }
    void change(int k) {
        if(l[k] == r[k]) {
            if(!flag[mid]) {
                q.push(mid);
            }
            flag[mid] = true;
            val[k] = false;
            return;
        }
        if(L <= mid && val[lc]) change(lc);
        if(R > mid && val[rc]) change(rc);
        push_up(k);
    }
} tree;
int main() {
    read(n, k);
    fu(i, 1, n) {
        read(a[i]);
    }
    tree.l[1] = 0, tree.r[1] = n + 1;
    tree.build(1);
    L = R = 1;
    tree.change(1);
    while(!q.empty()) {
        now = q.front();
        q.pop();
        if(~a[now]) {
            L = R = a[now];
            tree.change(1);
        }
        else {
            L = now + 1, R = min(now + k, n + 1);
            tree.change(1);
        }
    }
    fd(i, n + 1, 1) {
        if(flag[i]) {
            write(i);
            break;
        }
    }
    flush();
    return 0;
}

T4 card

不简单的问题。一直以为正解是搜索,把自己打的奇形怪状的搜索改来改去后发现可以通过样例便以为过了,然后走人了。

正解是区间dp,类似的题好像做过一道叫做字符串折叠的还是啥的,反正挺像的,就是说记录一个字母可以抵消掉 $w$ 中的哪一段,初始化按照开始给的 $n1$ 条克制关系初始化就行,转移的话枚举转移点再按照后面的 $n2$ 条转换关系做就行了。

难点是把这两个看似奇葩的关系联想到区间dp的初始化和转移方程上去,然后因为这道题我打歪解还调了1h最后只有30pts让我气笑了,所以这题不简单。

Code:

#include <bits/stdc++.h>
namespace VividCycle {
    #include <bits/stdc++.h>
    // #define getchar gc
    #define putchar pc
    #define fu(i, l, r) for(int i = l; i <= r; ++i)
    #define fd(i, l, r) for(int i = l; i >= r; --i)
    #define fo(i, v) for(const auto& i : v)
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    static char buffer[1 << 20 | 1]; static int outp = -1;
    void flush() {fwrite(buffer, 1, outp + 1, stdout), outp = -1;}
    void pc(const char& ch) {if(outp == (1 << 20)) flush(); buffer[++outp] = ch;}
    char gc() {static char ibuf[1 << 20], *p1 = ibuf, *p2 = ibuf; return p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, 1000010, stdin), p1 == p2) ? -1 : *p1++;}
    template<typename T> void read(T& x) {x = 0; int f = 1; char ch = getchar(); while(ch < '0' || ch > '9') f *= ((ch == '-') ? -1 : 1), ch = getchar(); while(ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar(); x *= f;}
    template<typename T, typename ...Args> void read(T& x, Args& ...args) {read(x), read(args...);}
    void write(const char& ch) {putchar(ch);} void write(const char s[]) {int len = strlen(s) - 1; fu(i, 0, len) putchar(s[i]);} void write(char s[]) {int len = strlen(s) - 1; fu(i, 0, len) putchar(s[i]);}
    template<typename T> void write(T x) {static char obuf[1 << 8]; static int p; p = 0; if(x < 0) x *= (putchar('-'), -1); if(!x) {putchar('0'); return;} while(x) obuf[++p] = x % 10 ^ 48, x /= 10; while(p) putchar(obuf[p--]);}
    template<typename T, typename ...Args> void write(T x, Args ...args) {write(x), write(args...);}
}
using namespace VividCycle;
int n, n1, n2;
char ch1, ch2, ch3, s[22];
bool restrain[26][26], change[26][26][26], dp[26][21][21];
int main() {
    read(n1, n2);
    while(n1--) {
        ch1 = getchar();
        while(ch1 < 'A' || ch1 > 'Z') ch1 = getchar();
        ch2 = getchar();
        while(ch2 < 'a' || ch2 > 'z') ch2 = getchar();
        restrain[ch1 - 'A'][ch2 - 'a'] = true;
    }
    while(n2--) {
        ch1 = getchar();
        while(ch1 < 'A' || ch1 > 'Z') ch1 = getchar();
        ch2 = getchar();
        while(ch2 < 'A' || ch2 > 'Z') ch2 = getchar();
        ch3 = getchar();
        while(ch3 < 'A' || ch3 > 'Z') ch3 = getchar();
        change[ch1 - 'A'][ch2 - 'A'][ch3 - 'A'] = true;
    }
    while(cin >> s + 1) {
        memset(dp, false, sizeof(dp));
        n = strlen(s + 1);
        fu(i, 1, n) {
            fu(j, 0, 25) {
                if(restrain[j][s[i] - 'a']) {
                    dp[j][i][i] = true;
                }
            }
        }
        #define r (l + len - 1)
        fu(len, 2, n) {
            fu(l, 1, n - len + 1) {
                fu(k, l, r - 1) {
                    fu(a, 0, 25) {
                        fu(b, 0, 25) {
                            fu(c, 0, 25) {
                                if(change[a][b][c]) {
                                    dp[a][l][r] |= (dp[b][l][k] && dp[c][k + 1][r]);
                                }
                            }
                        }
                    }
                }
            }
        }
        #undef r
        write(dp['S' - 'A'][1][n] ? "YES" : "NO", '\n');
    }
    flush();
    return 0;
}

T5 tree

不简单的问题。

在T3的线段树优化建图后,按惯性思维来讲,应该不会有线段树板题了,于是这题容易把人误导到树剖上去,难度指数型增长。

然后这道题的暴力还不好打,是吧@liuzimin?有人这道题的暴力调半天结果发现是欧拉序整错了,我不说是谁,但是@liuzimin 应该知道。

哦?正解是啥?线段树区间修改单点查询,懒标记存当前子树的奇数深度的 $\Delta$,对应地当前子树偶数层就是 $-\Delta$。

因为暴力都不好打,所以这道题是不简单的。

但是事实证明打暴力甚至不如打正解来的快,是吧?@liuzimin

Code:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, m, opt, u, v, cnt, fst[200001], lst[200001], a[200001], dep[200001], dfn[400001];
vector<int> g[200001];
struct Segment_Tree {
    int l[1600001], r[1600001], tag[1600001];
    #define lc (k << 1)
    #define rc ((k << 1) | 1)
    #define mid ((l[k] + r[k]) >> 1)
    void push_down(int k) {
        tag[lc] += tag[k], tag[rc] += tag[k];
        tag[k] = 0;
    }
    void build(int k) {
        if(l[k] == r[k]) return;
        l[lc] = l[k], r[lc] = mid, l[rc] = mid + 1, r[rc] = r[k];
        build(lc), build(rc);
    }
    void change(int k) {
        if(fst[u] <= l[k] && r[k] <= lst[u]) {
            tag[k] += v;
            return;
        }
        push_down(k);
        if(fst[u] <= mid) change(lc);
        if(lst[u] > mid) change(rc);
    }
    int ask(int k) {
        if(l[k] == r[k]) return a[dfn[l[k]]] + ((dep[dfn[l[k]]] & 1) ? tag[k] : -tag[k]);
        push_down(k);
        if(fst[u] <= mid) return ask(lc);
        else return ask(rc);
    }
} tree;
void dfs(int now, int father) {
    dep[now] = dep[father] + 1;
    fst[now] = ++cnt;
    dfn[cnt] = now;
    for(const auto& i : g[now]) {
        if(i != father) dfs(i, now);
    }
    lst[now] = ++cnt;
    dfn[cnt] = now;
}
int main() {
//  freopen("tree.in", "r", stdin);
//  freopen("tree.out", "w", stdout);
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
    for(int i = 1; i < n; ++i) {
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(1, 1);
    tree.l[1] = 1, tree.r[1] = cnt;
    tree.build(1);
    while(m--) {
        scanf("%d%d", &opt, &u);
        if(opt & 1) {
            scanf("%d", &v);
            if(!(dep[u] & 1)) v = -v;
            tree.change(1);
        }
        else printf("%d\n", tree.ask(1));
    }
    return 0;
}

T6 word

不简单的问题。

观察到 $n$ 和 $m$ 的范围都很小,猜测状态压缩,算了一下感觉过不了,但还是硬着头皮打了个状压。

大样例要 $15s$,于是开始疯狂卡常,乱卡的时候发现提前把状态存下来会快十倍,然后过掉了这道题。

怎么dp?简单,我们状压当前哪些字符串是「与众不同的」,然后看到 $n \leqslant 20 < 26$,口胡一下得出「只要更改一个字符,那么一定可以保证它变得与众不同」。

还有一种情况就是:假设当前某种字符有 $x$ 个,那么更改掉 $x - 1$ 个就可以让剩下那一个变得与众不同,显然,剩下那一个选权值最大的即可。

此题考察了选手对状压坚定不移的坚持、对评测机 $1s$ 跑 $10^{9}$ 的信念以及各种奇葩的卡常奇技淫巧,所以这道题是不简单的。

Code:

#include <stdio.h>
#include <string.h>
typedef long long ll;
ll max(const ll& a, const ll& b) {return a > b ? a : b;}
ll min(const ll& a, const ll& b) {return a < b ? a : b;}
ll n, m, cnt, mn[21], bits[2048], val[2048], c[21][21], bit[21][26], sum[21][26], mx[21][26], dp[1 << 20];
char s[21][25];
int main() {
//  freopen("word/example.in", "r", stdin);
//  freopen("word.in", "r", stdin);
//  freopen("word.out", "w", stdout);
    while(~scanf("%lld%lld", &n, &m)) {
        memset(bit, 0, sizeof(bit));
        memset(sum, 0, sizeof(sum));
        memset(mx, 0, sizeof(mx));
        memset(mn, 0x7f, sizeof(mn));
        memset(dp, 0x3f, sizeof(dp));
        dp[0] = 0;
        cnt = 0;
        for(int i = 1; i <= n; ++i) {
            scanf(" %s", s[i] + 1);
        }
        for(int i = 1; i <= n; ++i) {
            for(int j = 1; j <= m; ++j) {
                scanf("%lld", &c[i][j]);
                bit[j][s[i][j] - 'a'] |= (1 << (i - 1));
                sum[j][s[i][j] - 'a'] += c[i][j];
                mx[j][s[i][j] - 'a'] = max(mx[j][s[i][j] - 'a'], c[i][j]);
                mn[i] = min(mn[i], c[i][j]);
            }
        }
        for(int i = 1; i <= n; ++i) {
            ++cnt;
            bits[cnt] = 1 << (i - 1);
            val[cnt] = mn[i];
        }
        for(int i = 1; i <= m; ++i) {
            for(int j = 0; j < 26; ++j) {
                if(bit[i][j]) {
                    ++cnt;
                    bits[cnt] = bit[i][j];
                    val[cnt] = sum[i][j] - mx[i][j];
                }
            }
        }
        for(int i = 1; i <= cnt; ++i) {
            for(int j = 0; j < (1 << n); ++j) {
                dp[j | bits[i]] = min(dp[j | bits[i]], dp[j] + val[i]);
            }
        }
        printf("%lld\n", dp[(1 << n) - 1]);
    }
    return 0;
}

总结:这场 Trash Round 非常不简单,我做题的时候大意了,题还没偷袭我直接闪走了。

posted @ 2023-10-03 21:12  A_box_of_yogurt  阅读(10)  评论(0编辑  收藏  举报  来源
Document