• 2019牛客多校训练第一场题解


    2019牛客多校训练第一场题解

    题目链接

    A.Equivalent Prefixes

    考虑位置(i)为区间最小值的下标,那么只需要找到左边第一个值比它小的位置就行了。单调栈搞一搞就行。

    Code ```cpp #include using namespace std; const int N = 5e5 + 5; int a[2][N]; int n; int L[2][N]; int sta[2][N]; int main() { ios::sync_with_stdio(false); cin.tie(0); while(cin >> n) { for(int i = 1; i <= n; i++) cin >> a[0][i]; for(int i = 1; i <= n; i++) cin >> a[1][i]; for(int i = 1; i <= n; i++) L[0][i] = L[1][i] = i; L[0][n + 1] = -1; for(int k = 0; k < 2; k++) { int top = 0; for(int i = n; i >= 0; i--) { if(top == 0) { sta[k][++top] = i; continue ; } while(top && a[k][sta[k][top]] > a[k][i]) { L[k][sta[k][top]] = i; top--; } sta[k][++top] = i; } } for(int i = 1; i <= n + 1; i++) { if(L[0][i] != L[1][i]) { cout << i - 1 << ' '; break ; } } } return 0; } ```

    B. Integration

    题解参见:传送门

    Code
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 1e3 + 5, MOD = 1e9 + 7;
    int n;
    ll a[N];
    ll qp(ll A, ll B) {
        ll ans = 1;
        for(; B; B >>= 1) {
            if(B & 1) ans = ans * A % MOD;
            A = A * A % MOD;
        }
        return ans ;
    }
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        while(cin >> n) {
            for(int i = 1; i <= n; i++) cin >> a[i];
            ll ans = 0;
            for(int i = 1; i <= n; i++) {
                ll tmp = 2ll * a[i];
                for(int j = 1; j <= n; j++) {
                    if(i == j) continue ;
                    tmp = (tmp * (a[j] + a[i]) % MOD * (a[j] - a[i]) % MOD + MOD) % MOD;
                }
                ans = (ans + qp(tmp, MOD - 2)) % MOD;
            }
            cout << ans << '
    ';
        }
        return 0;
    }
    

    C.Euclidean Distance

    题解说的用拉格朗日乘子法,但蒟蒻不会= =
    后面知道直接贪心就行了,首先将(a_i)从大到小排个序,因为题目的条件:(sum{p_i}=1)(p_i>=0),我们可以先提取一个(m^2)出来,那么我们需要最小化的式子就变为了((a_i-m*p_i)^2)
    然后我们感性理解一下这个式子,相当于把数(m)分配,然后用(a_i)减去。所以直接贪心地想,每次不断减少当前最大的(a_i)就行了,如果有多个都最大就一起减就是了。

    Code
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 1e4 + 5;
    int n, m;
    int a[N] ;
    ll gcd(ll A, ll B) {
        return B == 0 ? A : gcd(B, A % B);
    }
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        while(cin >> n >> m) {
            for(int i = 1; i <= n; i++) cin >> a[i];
            ll fm, fz = 0;
            ll remain = m, cnt = 0, last;
            sort(a + 1, a + n + 1);
            for(int i = n; i >= 2; i--) {
                cnt++;
                int d = a[i] - a[i - 1];
                if(remain >= d * cnt) {
                    remain -= d * cnt;
                } else {
                    last = cnt * a[i] - remain;
                    remain = -1;
                    break ;
                }
            }
            if(remain >= 0) {
                cnt++;
                last = cnt * a[1] - remain;
            }
            fm = cnt * cnt;
            for(int i = n - cnt; i >= 1; i--) {
                fz += fm * a[i] * a[i];
            }
            fz += cnt * last * last; fm *= (m * m);
            ll g = gcd(fz, fm);
            fz /= g, fm /= g;
            if(fm == 1 || !fz) cout << fz << '
    ';
            else cout << fz << '/' << fm << '
    ';
        }
        return 0;
    }
    

    E.ABBA

    这题有两种解法,计数dp或者组合数学来搞。
    dp的话,设(dp[x][y])表示目前有(x)(A)(y)(B)且满足条件的前缀串的个数,那么转移方程就是(dp[x][y]=dp[x-1][y]+dp[x][y-1]),即分别考虑当前位为(A)(B)
    但并不是所有的状态都能转移过来,所以就需要一些限制,就以当前考虑放的是(A),已经有(x)(A)(y)(B)为例。因为要合法,所以就有限制条件:(y>=x+1-n),也就是说,先尽可能地(AB)配对,然后会剩下一些(A),这些就只能(BA)配对了,此时(B)的个数要不小于剩下的,如果小于就说明会多出一对(AB)了。
    另外一个限制条件同理可得。

    Code
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 2e3 + 5, MOD = 1e9 + 7;
    int n, m;
    ll dp[N][N];
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        while(cin >> n >> m) {
            for(int i = 0; i <= n + m; i++)
                for(int j = 0; j <= n + m; j++) dp[i][j] = 0;
            dp[0][0] = 1;
            for(int i = 0; i <= n + m; i++) {
                for(int j = 0; j <= n + m; j++) {
                    if(i + 1 - n <= j) dp[i + 1][j] = (dp[i + 1][j] + dp[i][j]) % MOD;
                    if(j + 1 - m <= i) dp[i][j + 1] = (dp[i][j + 1] + dp[i][j]) % MOD;
                }
            }
            cout << dp[n + m][n + m] << '
    ';
        }
        return 0;
    }
    
     

    组合数学的话核心思想就是容斥一下,所有情况减去不合法的,证明过程有点类似于卡特兰数的证明过程。
    所有的情况很显然为(C_{2*(n+m)}^{n+m}),对于不合法的情况,还是只拿一个举例,另一个同理。
    上面说过要满足(y>=x-n),不合法的话说明就存在一个有(x)(A)(y)(B)的前缀,满足(x=y+n+1)。然后我们将这个前缀的(A),(B)互换,会得到一个有(m-1)(A)(n+m+n+1)(B)的序列。而这两个序列是一一对应的。那么易知不合法的方案数就为(C_{2*(n+m)}^{m-1}),同理另一个为(C_{2*(n+m)}^{n-1})
    (n=0)(m=0)的情况单独考虑一下就行了,具体来推也是上面的方法。
    代码如下:

    Code
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N = 4e3 + 5, MOD = 1e9 + 7;
    int n, m;
    ll fac[N], inv[N];
    ll qp(ll a, ll b) {
        ll ans = 1;
        for(; b; b >>= 1) {
            if(b & 1) ans = ans * a % MOD;
            a = a * a % MOD;
        }
        return ans ;
    }
    ll C(ll a, ll b) {
        return fac[a] * inv[b] % MOD * inv[a - b] % MOD;
    }
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        fac[0] = inv[0] = 1;
        for(int i = 1; i < N; i++) fac[i] = fac[i - 1] * i % MOD, inv[i] = qp(fac[i], MOD - 2) ;
        while(cin >> n >> m) {
            ll ans = C(2 * (n + m), n + m);
            if(n) ans -= C(2 * (n + m), n - 1);
            if(m) ans -= C(2 * (n + m), m - 1);
            cout << (ans % MOD + MOD) % MOD << '
    ';
        }
        return 0;
    }
    

    F.Random Point in Triangle

    emmm,找到重心然后推一下就行了,可以用等边三角形来搞一搞。
    求期望面积,而底边确定,那么我们就只需要求期望高度,也就是要确定一个点,多边形可以看作一个质量均匀分布的物体,所以找重心就好了。
    详细证明可以参见:传送门
    代码如下:

    Code
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    ll a1, a2, a3, b1, b2, b3;
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        while(cin >> a1 >> b1 >> a2 >> b2 >> a3 >> b3) {
            ll x1 = a1 - a2, y1 = b1 - b2;
            ll x2 = a1 - a3, y2 = b1 - b3;
            ll ans = 11 * labs(x1 * y2 - x2 * y1);
            cout << ans << '
    ';
        }
        return 0;
    }
    
     

    H.XOR

    直接考虑子集不好思考,所以我们考虑每个数对答案的贡献。
    先插入(n)个数的线性基,得秩为(r),那么现在从非基底中选择一个,它与其余(n-r-1)个的组合的异或值都能被这(r)个基底表示(线性基的性质),这些非基底的贡献还是比较好算的。
    求基底的贡献就需要搞点事情了。一个基底有贡献也就等价于需要它来进行异或,也就是说它能被其余的数给异或出来。所以我们就可以枚举基底将其去掉,求出剩下(n-1)个数的基来看是否能够异或出我们枚举的(也可以直接用秩来判断),如果是,那么贡献就为(2^{n-r-1}),因为其余的组合也能被异或出;否则就没有贡献。
    这题的关键就是将问题转化为求每个数的贡献,其它的只要比较了解线性基以及异或的一些性质就行了。
    代码如下:

    Code
    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int MAX = 64, N = 1e5 + 5, MOD = 1e9 + 7;
    int n;
    ll a[N];
    struct linerbase{
        int SZ;
        ll v[MAX];
        void Clear() {
            SZ = 0, memset(v, 0, sizeof(v));
        }
        void Copy(linerbase a) {
            SZ = a.SZ; memcpy(v, a.v, sizeof(v));
        }
        bool Ins(ll x) {
            for(int i = 63; i >= 0; i--) {
                if(x >> i & 1) {
                    if(!v[i]) {
                        v[i] = x;
                        SZ++;
                        break;
                    } else x ^= v[i];
                }
            }
            return x > 0;
        }
    }b1, b2, b3;
    vector <int> ID;
    ll qp(ll A, ll B) {
        ll ans = 1;
        while(B) {
            if(B & 1) ans = A * ans % MOD;
            A = A * A % MOD;
            B >>= 1;
        }
        return ans;
    }
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        while(cin >> n) {
            for(int i = 1; i <= n; i++) cin >> a[i];
            ll ans = 0, cnt = 0;
            b1.Clear(), b2.Clear(), b3.Clear(), ID.clear();
            for(int i = 1; i <= n; i++) {
                if(!b1.Ins(a[i])) {
                    b2.Ins(a[i]);
                } else {
                    ID.push_back(i);
                }
            }
            if(b1.SZ != n) {
                cnt = qp(2, n - b1.SZ - 1);
                ans = (ans + cnt * (n - b1.SZ) % MOD) % MOD;
            }
            for(auto i : ID) {
                b3.Copy(b2);
                for(auto j : ID) if(i != j) b3.Ins(a[j]);
                if(b3.SZ == b1.SZ) ans = (ans + cnt) % MOD;
            }
            cout << ans << '
    ';
        }
        return 0;
    }
    

    I.Points Division

    不是很懂的线段树+dp。
    分析题目条件可以知道如果一个点属于集合(B),那么其右下角的所有点都必须属于(B)集合。那么最后就会形成一个不降的折线段,之后就在这上面dp。
    排序后枚举每个点然后分情况来转移,如果这个点在折线段上面,那么就从(1)(y_i)上面找一个最大值mx,进行转移(dp[y_i]=mx+b_i);否则,我们就需要对之前的dp值进行更新,对于所有(y)值大于(y_i)的就加上(b_i),小于的就加上(a_i)
    可以证明这样来搞是不会损失最优解的。
    代码如下:

    Code
    #include <bits/stdc++.h>
    #define INF 0x3f3f3f3f
    using namespace std;
    typedef long long ll;
    const int N = 1e5 + 5;
    int n;
    struct node{
        int x, y;
        ll a, b;
        bool operator < (const node &A)const {
            if(A.x == x) return y > A.y;
            return x < A.x;
        }
    }p[N];
    int Hash[N];
    ll maxx[N << 2], add[N << 2];
    void push_up(int o) {
        maxx[o] = max(maxx[o << 1], maxx[o << 1|1]);
    }
    void push_down(int o) {
        if(add[o]) {
            add[o << 1] += add[o];
            add[o << 1|1] += add[o];
            maxx[o << 1] += add[o];
            maxx[o << 1|1] += add[o];
            add[o] = 0;
        }
    }
    void build(int o, int l, int r) {
        add[o] = 0;
        if(l == r) {
            maxx[o] = 0;
            return ;
        }
        int mid = (l + r) >> 1;
        build(o << 1, l, mid), build(o << 1|1, mid + 1, r);
        push_up(o);
    }
    ll query(int o, int l, int r, int L, int R) {
        if(L <= l && r <= R) return maxx[o];
        push_down(o);
        int mid = (l + r) >> 1;
        ll mx = 0;
        if(L <= mid) mx = max(mx, query(o << 1, l, mid, L, R));
        if(R > mid) mx = max(mx, query(o << 1|1, mid + 1, r, L, R));
        return mx;
    }
    void update(int o, int l, int r, int k, ll v) {
        if(l == r && l == k) {
            maxx[o] = v;
            return ;
        }
        push_down(o);
        int mid = (l + r) >> 1;
        if(k <= mid) update(o << 1, l, mid, k, v);
        else update(o << 1|1, mid + 1, r, k, v);
        push_up(o);
    }
    void update2(int o, int l, int r, int L, int R, int v) {
        if(L <= l && r <= R) {
            maxx[o] += v;
            add[o] += v;
            return ;
        }
        push_down(o);
        int mid = (l + r) >> 1;
        if(L <= mid) update2(o << 1, l, mid, L, R, v);
        if(R > mid) update2(o << 1|1, mid + 1, r, L, R, v);
        push_up(o);
    }
    int main() {
        ios::sync_with_stdio(false); cin.tie(0);
        while(cin >> n) {
            int D = 0;
            Hash[++D] = -INF, Hash[++D] = INF;
            for(int i = 1; i <= n; i++) {
                cin >> p[i].x >> p[i].y >> p[i].a >> p[i].b;
                Hash[++D] = p[i].y;
            }
            sort(p + 1, p + n + 1);
            sort(Hash + 1, Hash + D + 1);
            D = unique(Hash + 1, Hash + D + 1) - Hash - 1;
            for(int i = 1; i <= n; i++) p[i].y = lower_bound(Hash + 1, Hash + D + 1, p[i].y) - Hash;
            build(1, 1, D);
            for(int i = 1; i <= n; i++) {
                ll mx = query(1, 1, D, 1, p[i].y) ;
                update(1, 1, D, p[i].y, mx + p[i].b);
                update2(1, 1, D, 1, p[i].y - 1, p[i].a);
                update2(1, 1, D, p[i].y + 1, D, p[i].b);
            }
            cout << query(1, 1, D, 1, D) << '
    ';
        }
        return 0;
    }
    

    J.Fraction Comparision

    python大法好。

    Code
    while True:
        try:
            x,a,y,b = map(int,input().split())
            x = x * b
            y =y *a
            if x==y:
                print("=")
            elif x<y:
                print('<')
            else:
                print('>')
        except:
            break;
    
  • 相关阅读:
    strip()、rstrip()和lstrip()
    Vim 中快速移动系列(1)
    Python中的read(), readline(), readlines()
    Python 列表解析(列表生成式)
    Python lambda 表达式介绍
    Python中sort()和sorted()的区别
    js 高级
    maven学习笔记
    Maven之settings.xml详解
    Eclipse 学习笔记
  • 原文地址:https://www.cnblogs.com/heyuhhh/p/11215696.html
Copyright © 2020-2023  润新知