EDU119(A-E) 题解

Educational Codeforces Round 119 (Rated for Div. 2)

A. Equal or Not Equal

题目大意

给你一个字符串\(s\),仅由EN组成,其中:

  • \(s_i = E\),代表\(a_i = a_{i+1}\)
  • \(s_i = N\),代表\(a_i \neq a_{i+1}\)

求问对于给定的模式串是否找到一个可能的字符串。

思路

发现,假如只存在一个\(N\)时,一定首和尾的冲突,否则不会。

因此代码很简单,只需要统计N的个数,判断是否为1

B. Triangles on a Rectangle

题目大意

给定一个左下角为原点\(O(0, 0)\),右上角\(P(w,h)\)的矩形,给定一些点并且保证给定的点不在corner。

给出四行位于矩形边沿的点分别满足:\((tmp, 0), (tmp, h), (0, tmp), (w,tmp)\)

求出满足如下要求的面积最大三角形,面积的两倍必须有两个点位于矩形的同一边上

思路

每个边维护最小,最大两个点,底为: \(mx - mn\)

对于在 \(x\) 相同的点,其高肯定是\(h\)

对于在 \(y\) 相同的点, 其高肯定是\(w\)

代码如下,之前的代码太傻了。

... 宏定义区域
void solve(){
    ll w, h;
    read(w, h);

    ll ans = 0;
    vt<ll> len{h, w};

    rep (a, 0, 2){
        rep (b, 0, 2){
            int k; read(k);
            int x, mn = inf, mx = -inf;
            rep (i, 0, k) read(x), mn = min(mn, x), mx = max(mx, x);
            ans = max(ans, (mx - mn) * len[a]);
        }
    }

    std::cout << ans << "\n";
}

C. BA-String

题目大意

给定一个模式串 \(p\),满足:

  • 只有 a* 两种符号,其中 * 可以扩展为最多 \(k\)b(可以为0个)。

求出字典序第\(x\)小的文本串。

Constraint: \(1 \le n \le 2000;0\le k\le 2000;1\le x\le10^{18}\)

思路

首先注意ll的使用和溢出问题。

首先考虑如何构造不同的文本串\(t\),假设 a 是分隔符,那么找出所有由 a 分隔的区间 \(seg\) 。则总数为:

\[tot = \Pi |seg_i| \]

显然,我们需要第\(x\)小,就是从后往前,分配每一个段内的个数,比如:

**a***, 我们需要找到第 \(x=20\) 小,其中包含两个区间 \(seg = [2, 3] \rightarrow sz = [7, 10]\),

为了方便取mod,我们不妨让\(x=x-1\)

从后往前,对于倒数第一个区间 \(|seg_2| = 10\):使用了 \(19 \% 10 = 9\)b,留下了 \(x=19//10=1\)到下一个区间。

从后往前,对于倒数第二个区间\(|seg_1|=7\) :使用了 \(1 \% 7 = 1\)b, 留下了\(0\)个,说明结束了。

所以最后答案为:babbbbbbbbb

注意,从前往后思路也可以,但是考虑到最多有\(k^{n-1}\)的种类,可能会爆\(ll\),在这种种类很多的题目中,记得考虑是否会溢出的问题,考虑一个不可能会溢出的角度去解决这个问题。

void solve(){
    string p;
    ll n, k, x;
    read(n, k, x);
    read(p);

    string ans = "";
    ll m = p.length(), base = 1, st = 0;
    for (int i = m - 1; i >= -1; -- i){
        if (x == 0) break;
				// i == -1 是为了处理以*开头的情况。
        if (i == -1 || p[i] == 'a'){
            if (st != 0){
                ll nxt, lvf;
                base = (k * st + 1);
                nxt = (x + base - 1) / base;
                lvf = x - base * (nxt - 1);
                ans += string(lvf - 1, 'b');
                x = nxt;
            }
            st = 0;
            if (i != -1) ans += "a";
        }else ++ st;
    }

    reverse(all(ans));
    std::cout << ans << "\n";
}

D. Exact Change

题目大意

\(t|t\in[1, 1000]\)case,有 \(n|n\in [1, 100]\) 种炸薯条,每个薯条的价格是 \(a_i|a_i \in [1,10^9]\),你只有三种硬币,面额分别是:\(1, 2, 3\)

求问,你至少带多少枚硬币可以保证能买任何一种炸薯条

思路

首先,对于这种能否恰好换钱的问题,容易想到背包贪心

显然,我们优先得用\(3\),但是细节之处\(1, 2\) 也会有用。

比如: \(6, 2, 1\), 我们最少拿 \(3\) 而不是 \(4\) 枚硬币:我们可以拿 \(\{3, 2, 1\}\) 便可以购买每个炸薯条。

但是显然,\(1, 2\) 的个数不会很多,并且最终的结果一定在 \(\dfrac{mx}{3}\) 左右浮动。

由于数据范围很小,我们可以考虑暴力枚举 \(1, 2\) 的个数(都小于 \(2\)),同时暴力枚举 \(2\) 的使用个个数,在考虑 \(1\) 的个数使得最终 \(x \% == 0\) 。并记录最少需要的硬币总数。

或者,直接枚举总数,枚举\(1,2\)的数量然后用背包求出一个大小为6背包的使用情况,再枚举\(3\)的使用情况(只需要\([mx-2, mx]\),因为背包最多 \(6\) 恰好等于两个 \(3\) 我就是这个方法,但是不够简洁。

void solve(){
    int n; read(n);
    vt<int> f(n); rep (i, 0, n) read(f[i]);

  	// 最少用量
    int mincoins = inf;
    for (int one = 0; one <= 2; ++ one){
        for (int two = 0; two <= 2; ++ two){
            // 要使得每个都能被凑齐的 3 的个数
            int maxthree = 0;
            for (int i = 0; i < n; ++ i){
              	// 不同 1, 2 使用个数对应的最少 3 的个数
                int minthree = inf;
                for (int use2 = 0; use2 <= two; ++ use2){
                    int x = f[i], lvf;
                    if (x - 2 * use2 < 0) continue;
                    x -= 2 * use2;
                    lvf = x % 3;
                    if (lvf > one) continue;
                    minthree = min(minthree, x / 3);
                }
                maxthree = max(maxthree, minthree);
            }
            mincoins = min(mincoins, one + two + maxthree);
        }
    }

    wpr(mincoins);
}

关于背包的解法放过链接:🔗

E. Replace the Numbers

题目大意

初始给定一个空的数组\(arr\),给定\(q|q\in[1, 5\cdot 10^5]\)个操作:

  • 1 x,代表往\(arr\)中添加一个元素 \(x|x\in[1, 5\cdot 10^5]\)
  • 2 x y,代表\(arr\)中的所有 \(x\) 全都变为 \(y\)

问最后的数组长什么样。

看起来有点像 DSU,但是考虑:

1 1
1 1
1 2
2 1 3
1 1

假如,直接 DSU 就会变成:\([3, 3, 2, 3]\)

因此,我们需要考虑如何处理使得我们能考虑到 op2 的实效性。

首先,可以考虑暴力(启发式合并),目前我不清楚如何完全搞清楚这个思路的时间复杂度,但是其在多次合并数据结构时比较好用,也就是我们每次把的合并到的中,这样可以保证多次合并过程中每个元素最多被移动\(\log n\)次(\(n\) 代表总元素)。

考虑正常的做法,由于增加操作是单调的,因此我们也可以倒过来做。

例如:

1 3
2 3 4
1 3
2 3 5
1 3

倒过来做,先考虑 1 3 则结果数组为:ans = [3]

考虑 2 3 5,此前的3将会变为5目前代表的东西,类似并查集:pa[3] = pa[5]

考虑 1 3,添加pa[3] = pa[5] = 5,结果数组为:ans = [3, 5]

考虑 2 3 4,此前的3将会变成4目前代表的东西,pa[3] = pa[4] = 4

考虑1 3,添加pa[3] = pa[4] = 4,结果数组为:ans = [3, 5, 4]

倒过来就好了。

const int N = 5e5 + 50;
void solve(){
    int q; std::cin >> q;
    vt<int> t(q), x(q), y(q);
    rep (i, 0, q){
        std::cin >> t[i];
        if (t[i] == 1) std::cin >> x[i];
        else std::cin >> x[i] >> y[i];
    }

    vt<int> p(N);
    iota(all(p), 0);
    vt<int> ans;
    per (i, q - 1, 0){
        if (t[i] == 1) ans.pb(p[x[i]]);
        else {
            p[x[i]] = p[y[i]];
        }
    }

    reverse(all(ans));
    for (int i = 0, _n = ans.size(); i < _n; ++ i)
        std::cout << ans[i] << " \n"[i == _n - 1];
}

还有一种,正向直接做,巧妙的用了时间Tag和并查集去实现,链接🔗

posted @ 2021-12-27 18:32  Last_Whisper  阅读(61)  评论(0编辑  收藏  举报