2020-12-18

2020蓝桥杯选拔赛题解

A

因为两条线段是平行于 x x x轴的, 只要考虑这两条线段再 x x x轴的投影是否相交。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

int main() {
    ll x[5], y[3];
    cin >> x[1] >> x[2] >> x[3] >> x[4] >> y[1] >> y[2];
    // l和r是那个直线的相交的线段
    ll l = max(x[1], x[3]);
    ll r = min(x[2], x[4]);
    if (l <= r) {
        puts("Yes");
    } else {
        puts("No");
    }

}

B

考虑一种贪心的思想, 每次选费用最少的牌,一定可以取最多, 那么可以将费用从小到大排序,每次都取最小的。 每打出一张牌其它费用都减一, 这个可以用个计数器记录一下当前已经减了多少。时间复杂度为 o ( n ∗ l o n g ( n ) ) o(n * long(n)) o(nlong(n))

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 7;
int n, a[N];

ll x;

int main() {
    cin >> n >> x;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    sort(a + 1, a + n + 1);
    int sum = 0;    // 记录已经减了多少。
    int ans = 0;
    for (int i = 1; i <= n; i++) {
        a[i] = max(a[i] - sum, 0);  // 减到0就不会在减了
        if (x >= a[i]) {
            x -= a[i];
            ans++;
        } else {
            break;
        }
        sum++;
    }
    cout << ans << endl;
}

C

这题简单模拟。直接看代码吧。

#include<bits/stdc++.h>
using namespace std;
string str;
typedef long long ll;

int main() {

    
    cin >> str;
    ll x = 0, y = 0;
    ll w = 1, s = 1, a = 1, d = 1;
    for (int i = 0; i <str.length(); i++) {
        if (str[i] == 'W') {
            x += w;
            w++;
            s = 1;
            a = 1;
            d = 1;
        } else if (str[i] == 'S') {
            x -= s;
            s++;
            w = 1;
            a = 1;
            d = 1;

        } else if (str[i] == 'A') {
            y -= a;
            a++;
            w = 1;
            d = 1;
            s = 1;
        } else {
            y += d;
            d++;
            a = 1;
            w = 1;
            s = 1;
        }
    }
    cout << x << " " << y << endl;
}

D

查找一个字符串的子序列的数量是一个很常见的 d p dp dp问题。

但是这题可以用组合数学来解决。

枚举每个字符, 当遇到字符 S S S时,只要记录从开始到当前位置有多少个 C C C,再记录从当前位置到最后面的位置

有多少个 U U U。假设从开始到当前位置有 x x x个字符 C C C, 从当前位置到最后面有 y y y U U U,那么这个字符 S S S对答案的贡献就是

C x 2 ∗ y C_x ^{2} * y Cx2y

说一下 d p dp dp怎么写, 设 d p [ 0 ] dp[0] dp[0]表示当前位置有多少个字符 C C C d p [ 1 ] dp[1] dp[1]表示有多少个字符 C C CC CC (这里的 C C CC CC指的是子序列), d p [ 2 ] dp[2] dp[2]表示有多少个 C C S CCS CCS(这里的 C C S CCS CCS也是子序列), d p [ 3 ] dp[3] dp[3]表示有多少个 C C S U CCSU CCSU(同理也是子序列)。

然后方程怎么转移?

d p [ 0 ] + = 1 dp[0] += 1 dp[0]+=1 当且仅当当前字符串为 C C C

d p [ 1 ] + = d p [ 0 ] dp[1] += dp[0] dp[1]+=dp[0] 当且仅当当前字符串为 C C C

d p [ 2 ] + = d p [ 1 ] dp[2] += dp[1] dp[2]+=dp[1]当且仅当当前字符串为 S S S

d p [ 3 ] + = d p [ 2 ] dp[3] += dp[2] dp[3]+=dp[2]当且仅当当前字符串为 U U U

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
string s;

ll dp[4];
const ll mod = 1e9 + 7;



int main() {
    cin >> s;
    for (int i = 0; i < s.length(); i++) {
        if (s[i] == 'C') {
           
            dp[1] += dp[0]; 
            dp[0] += 1;
        } else if (s[i] == 'S') {
            dp[2] += dp[1];
            
        } else if (s[i] == 'U') {
            dp[3] += dp[2];
            
        }
        dp[0] = dp[0] % mod;
        dp[1] = dp[1] % mod;
        dp[2] = dp[2] % mod;
        dp[3] = dp[3] % mod;
    }
    cout << dp[3] << endl;
}

E

每个物品可以考虑选和不选,那么总共也就 2 n 2 ^ n 2n种购买方案, 但是 n ≤ 32 n\le 32 n32 也就是最坏的情况时间复杂度为 o ( 2 32 ) o(2 ^{32}) o(232)大概是$ 4 * 10^9$左右时间复杂度肯定过不了。

所以考虑折半搜索。

n n n个物品分为两部分, [ 1 , n 2 ] [1, \frac {n}{2}] [1,2n] 的物品归为一类, 和 [ n 2 + 1 , n ] [\frac {n}{2} + 1, n] [2n+1,n]的物品归为一类。

那么现在就可以枚举第一类的所有可能尽快, 也就 2 n 2 2 ^{\frac {n}{2}} 22n种, 将所有可能用vetor存起来, 再枚举第二类的境况同理。

然后将第二类的所有可能按从小到大排序。

因为购买的商品总价格超过 x x x, 那么可以枚举第一类的所有方案, 假设第一类中第一个枚举的数是 y y y因为要大于 x x x, 所以第二类大于 x − y x - y xy的都合法, 这个可以用二分算贡献。

所以总共的时间复杂度为 O ( 2 n 2 ∗ l o n g ( n ) ) O(2 ^ {\frac {n} {2}}* long(n)) O(22nlong(n))

代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 50;
typedef long long ll;

ll a[N], n, x;

vector<ll> first, second;

void dfs(int p, ll sum, int count) {
    if (p > count) {
        if (count == n / 2) {
            first.push_back(sum);
        } else {
            second.push_back(sum);
        }
        return;
    }
    dfs(p + 1, sum + a[p], count);
    dfs(p + 1, sum, count);
}


int main() {
    cin >> n >> x;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    dfs(1, 0, n / 2);
    dfs(n / 2 + 1, 0, n);
    sort(second.begin(), second.end());
    ll ans = 0;
    for (int i = 0; i < first.size(); i++) {
        ll value = first[i];
        int p = lower_bound(second.begin(), second.end(), x - value) - second.begin();
        if (p < second.size()) {
            ans += 1ll*(second.size() - p);
        }
    }
    cout << ans << endl;

}

F

考虑 dfs序建线段树, 对于操作2可以用线段树的区间修改, 对于操作3怎么办?

因为对于儿子结点dfs序不连续, 没法做到区间修改, 暴力修改?如果是菊花图,复杂度直接过不了。

因为操作1, 询问的是当前结点为根的子树所有点的权值和, 所以操作可以看成单点操作, 将当前询问点值乘上儿子节点个数,就可以了, 询问的时候再记录一个父亲节点已经加了多少。

#include<bits/stdc++.h>
using namespace std;
const int N = 5e5 + 7;
typedef long long ll;
vector<int> g[N];

ll a[N], n, q;

int id[N], cnt = 1, fa[N], sz[N], son[N];

ll tree[4 * N], flag[4 * N], sum[N];

void dfs(int u, int f) {
    id[u] = cnt++;
    fa[u] = f;
    sz[u] = 1;
    for (int to: g[u]) {
        if (to == f) continue;
        dfs(to, u);
        son[u]++;
        sz[u] += sz[to];
    }
}

#define m (l + r) / 2
#define lson 2 * node
#define rson 2 * node + 1


void push_down(int node, int nl, int nr) {
    if (flag[node]) {
        tree[lson] += flag[node] * 1ll * nl;
        tree[rson] += flag[node] * 1ll * nr;
        flag[lson] += flag[node];
        flag[rson] += flag[node];
        flag[node] = 0;
    }
}

void update(ll v, int ql, int qr, int l, int r, int node) {
    if (ql <= l && qr >= r) {
        tree[node] += 1ll * (r - l + 1) * v;
        flag[node] += v;
        return;
    }
    push_down(node, m - l + 1, r - m);
    if (ql <= m) update(v, ql, qr, l, m, lson);
    if (qr > m) update(v, ql, qr, m + 1, r, rson);
    tree[node] = tree[lson] + tree[rson];
}

ll query(int ql, int qr, int l, int r, int node) {
    if (ql <= l && qr >= r) {
        return tree[node];
    }
    ll ans = 0;
    push_down(node, m - l + 1, r - m);
    if (ql <= m) ans += query(ql, qr, l, m, lson);
    if (qr > m) ans += query(ql, qr, m + 1, r, rson);
    return ans;
}


int main() {
    ios::sync_with_stdio(0);
    cin >> n >> q;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    for (int i = 1; i < n; i++) {
        int u, v; cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    dfs(1, 0);
    for (int i = 1; i <= n; i++) {
        update(a[i], id[i], id[i], 1, n, 1);
    }
    while (q--) {
        int op; cin >> op;
        if (op == 1) {
            int u; cin >> u;
            cout << query(id[u], id[u] + sz[u] - 1, 1, n, 1) + sum[fa[u]] << endl;
        } else if (op == 2) {
            int u, v; cin >> u >> v;
    
            update(v, id[u], id[u] + sz[u] - 1, 1, n, 1);
        } else {
            int u, v; cin >> u >> v;
            sum[u] += v;
            update(v * 1ll*(son[u] + 1), id[u], id[u], 1, n, 1);
        }
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值