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

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

题目链接

A.Equivalent Prefixes

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

Code

#include <bits/stdc++.h>
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 << '\n';
                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 << '\n';
    }
    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 << '\n';
        else cout << fz << '/' << fm << '\n';
    }
    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] << '\n';
    }
    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 << '\n';
    }
    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 << '\n';
    }
    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 << '\n';
    }
    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) << '\n';
    }
    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;

转载于:https://www.cnblogs.com/heyuhhh/p/11215696.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值