其他分享
首页 > 其他分享> > 2022黑龙江省赛题解(The 17th Heilongjiang Provincial Collegiate Programming Contest)

2022黑龙江省赛题解(The 17th Heilongjiang Provincial Collegiate Programming Contest)

作者:互联网

A - Bookshelf Filling

题意:

有a,b两种高度的书,a < b,a有n本,b有m本,全部竖着摆放在高度为h的书架上,现在要把至多(m - 1)本b书横着摆放在一些书的顶部。

问这样摆放之后的最小宽度是多少。

题解:

最简单的方法就是二分。这道题显然具有单调性,因为假设一个答案x可行,我们在中间抽出一本书放在右边,答案为x+1就可行。

check很简单,直接判断左边能放多少本就行了。

查看代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
int a, b, n, m, h;
int check(int x) {
    int la = n, lb = x - n;
    int cnt = n + m - x;
    int cap = 0;
    cap += (b - a) * (n / b);
    if(cap >= cnt) return 1;
    cap += (h - b) * (x / b);
    return cap >= cnt;
}
void work() {
    cin >> a >> b >> n >> m >> h;
    int l = n + 1, r = m + n;
    while(l <= r) {
        int Mid = (l + r) / 2;
        if(check(Mid)) r = Mid - 1;
        else l = Mid + 1;
    }
    cout << l << endl;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int Case;
    cin >> Case;
    while(Case--) work();
    return 0;
}

C - Tree Division

题意:

问能否将树上每个点标上A,B的编号(黑白染色),同时满足:对于任意从根节点出发的路径,A点形成了严格递减序列,B点形成了严格递增序列。

题解:

一开始没想到树形dp,以为有啥构造方法。确定是树形dp之后就很简单了。

我们考虑子树方案,显然根节点会被染上一种颜色,假设是A。

我们发现,根节点一定是子树内最小的元素。而如果对于根节点在原树的父亲节点,假如它染成A颜色,只需要考虑他是不是比根节点小,如果染成B颜色,则需要考虑子树内最大的B颜色是多大,所以我们只需要考虑子树内最大的被染成B的元素是多大就行了。

最直观的方法是$f[i][j]$表示以i为根的子树,最大B元素是j,是否实现。这个显然浪费了很多空间和时间,我们发现只需要关注最大的一个B元素最小能达到多少就行了。

我们修正状态$f[i][0]$表示以i为根的子树,根节点被染成A颜色,时,子树内最大的B元素最小是多少。

同理,$f[i][1]$就是以i为根的子树,根节点被染成B颜色,时,子树内最小的A元素最大是多少。

只考虑$f[i][0]$,我们考虑它的每一棵子树进行转移。

如果子树的根节点大于当前节点,说明可以从$f[j][0]$进行转移;

如果$f[j][1]$大于当前节点,那么可以从子树树根的值进行转移。

两者先取最小,再跟根节点取最大值,就完成转移了。

查看代码
//
// Created by onglu on 2022/7/14.
//

#include <bits/stdc++.h>

#define all(a) a.begin(),a.end()
#define rall(a) a.rbegin(),a.rend()

#define endl '\n'
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define Mid ((l + r) / 2)
//#define int long long
using namespace std;
const int N = 2e6 + 1009;
//const int N = 2e5 + 1009;
//const int N = 5009;
//const int N = 309;
int n, m, a[N], f[N][2];
vector<int> ver[N];
void dfs(int x, int pre) {
    f[x][0] = -1;
    f[x][1] = n + 1;
    for(auto y : ver[x]) if(pre != y) {
        dfs(y, x);
        int chose = n + 1;
        if(a[y] > a[x]) {
            chose = min(chose, f[y][0]);
        }
        if(f[y][1] > a[x]) {
            chose = min(chose, a[y]);
        }
        f[x][0] = max(f[x][0], chose);

        chose = -1;
        if(a[y] < a[x]) {
            chose = max(chose, f[y][1]);
        }
        if(f[y][0] < a[x]) {
            chose = max(chose, a[y]);
        }
        f[x][1] = min(f[x][1], chose);
    }
}
void work() {
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int i = 1; i < n; i++) {
        int x, y;
        cin >> x >> y;
        ver[x].push_back(y);
        ver[y].push_back(x);
    }
    dfs(1, 1);
    if(f[1][0] != n + 1 || f[1][1] != -1) {
        cout << "YES" << endl;
    } else {
        cout << "NO" << endl;
    }
}

signed main() {
#ifdef LOCAL
    freopen("C:\\Users\\onglu\\CLionProjects\\acm\\data.in", "r", stdin);
    freopen("C:\\Users\\onglu\\CLionProjects\\acm\\data.out", "w", stdout);
#endif
    ios::sync_with_stdio(false);
    cin.tie(0);
    int Case = 1;
    // cin >> Case;
    while(Case--) work();
    return 0;
}

F - 342 and Xiangqi

题意:

有两个没有区别的象在棋盘上,问走到对应的位置需要几步。

题解:

什么最短路,什么考虑象按顺序移动不影响,反正没几步就暴力bfs呗,懒得思考了。

查看代码
 #include <bits/stdc++.h>
#define endl '\n'
using namespace std;

vector<vector<int> > v;
int n, m, s, t;
map<pair<int, int>, int> M;
void work() {
    cin >> n >> m >> s >> t;
    if(s > t) swap(s, t);
    if(n > m) swap(n, m);
    M.clear();
    queue<pair<int, int> > q;
    q.push({n, m});
    M[{n, m}] = 0;
    while(q.size()) {
        int x = q.front().first;
        int y = q.front().second;
        int d = M[{x, y}];
        if(x == s && y == t) {
            cout << d << endl;
            return ;
        }
        q.pop();
        for(auto z : v[x]) if(z != y) {
            int tx = y, ty = z;
            if(tx > ty) swap(tx, ty);
            if(M.count({tx, ty})) continue;
            M[{tx, ty}] = d + 1;
            q.push({tx, ty});
        }
        for(auto z : v[y]) if(z != x) {
            int tx = x, ty = z;
            if(tx > ty) swap(tx, ty);
            if(M.count({tx, ty})) continue;
            M[{tx, ty}] = d + 1;
            q.push({tx, ty});
        }
    }
}
signed main()
{
    v.push_back(vector<int> {});
    v.push_back(vector<int> {2, 3});
    v.push_back(vector<int> {1, 4});
    v.push_back(vector<int> {1, 4});
    v.push_back(vector<int> {2, 3, 5, 6});
    v.push_back(vector<int> {4, 7});
    v.push_back(vector<int> {4, 7});
    v.push_back(vector<int> {5, 6});
    ios::sync_with_stdio(0);
    cin.tie(0);
    int Case;
    cin >> Case;
    while(Case--) work();
    return 0;
}

G - Chevonne's Necklace

 题意:

有一个环状序列,每个位置有一个值$c_i$,选定i位置的值,会导致它和它之后$(c_i-1)$个元素被删除(贡献是$c_i$),删除后序列会并拢,不会出现空隙。

问最多删除几个贡献,以及方案数。

两个方案不同,当且仅当被选定的位置的集合不同。

题解:

一开始没看到集合不同,想了快有两个小时。。。

这题似乎在lyd的书上见过,反正就是你任意选定总和不超过$n$的一个集合,你一定有办法把它删干净。

证明也很简单:因为总和不超过n,所以一定不存在循环依赖(你可以把每个元素想象成区间,假如存在循环依赖,总和一定大于n,因为每个元素都被覆盖至少一次)。

那么我们从一个一端元素只被覆盖一次的区间开始删除,那么就能删完了。

 

然后问题变成了选定总和不超过$n$的元素的方案数。裸的背包。

查看代码
 #include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 998244353;
int f[2009], g[2009], c[2009], n;
signed main()
{
    ios ::sync_with_stdio(0);
    cin.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i++) {
        cin >> c[i];
    }
    f[0] = 1;
    g[0] = 1;
    for(int i = 1; i <= n; i++) if(c[i] != 0) {
        for(int j = n - c[i]; j >= 0; j--) {
            f[j + c[i]] = (f[j + c[i]] + f[j]) % mod;
            g[j + c[i]] |= g[j];
        }
    }
    for(int i = n; i >= 0; i--) if(g[i]) {
        cout << i << " " << f[i] << endl;
        return 0;
    }
    cout << 0 << endl;
    return 0;
}

H - Kanbun

题意:

给定一个序列,包含'(','-',')',然后输出顺序是:

遇到左括号,跳过,直到输出与他匹配的右括号后,再输出它。

遇到其他元素直接跳过。

题解:

直接递归处理就行了。

先预处理出每个左括号对应的右括号,遇到左括号的时候,先递归输出区间内容,然后输出右括号,最后输出左括号。

查看代码
 #include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 1009;

int n, nxt[N];
char c[N];
void dfs(int l, int r) {
    if(l > r) return ;
    if(c[l] != '(') {
        cout << l << " ";
        dfs(l + 1, r);
    } else {
        dfs(l + 1, nxt[l] - 1);
        cout << nxt[l] << " ";
        cout << l << " ";
        dfs(nxt[l] + 1, r);
    }
}
signed main()
{
    ios ::sync_with_stdio(0);
    cin.tie(0);
    cin >> n;
    cin >> (c + 1);
    stack<int> S;
    for(int i = 1; i <= n; i++) {
        if(c[i] == '(') S.push(i);
        else if(c[i] == '-') {
            nxt[i] = i;
        } else {
            nxt[S.top()] = i;
            nxt[i] = i;
            S.pop();
        }
    }
    dfs(1, n);
    return 0;
}

I - Equal Sum Arrays

题意:

给定一个数$n$,问有多少个正整数序列,满足序列所有元素和等于$n$。

题意:

n < 20,直接暴搜呗,手快的人这时候已经打完了。

正解的话,可以发现这就是n个数的隔板法,直接输出$2^{n - 1}$

查看代码
 #include <bits/stdc++.h>
using namespace std;
int n, ans = 0;
void dfs(int res) {
    if(res == 0) {
        ans += 1;
        return ;
    }
    for(int i = 1; i <= res; i++) {
        dfs(res - i);
    }
}
signed main()
{
    cin >> n;
    dfs(n);
    cout << ans << endl;
    return 0;
}

L - Let's Swap

题意:

给定一个字符串,我们可以进行以下操作:

将这个字符串$[1,x],[x + 1, n]$分别进行$reverse$。  

但是这个$x$只能取$l_1,l_2$,问给一个s,能否变成t。  

题解:

玩一下可能更能明白这道题的意思:

假设对$l_1$进行翻转称为A操作,另一个称为$B$操作。

我们发现每个操作的逆操作是自身,并且操作满足结合律。

也就是说ABBA = 没操作。

这样代表,我们的操作只能是ABABABABABABA或者BABABABABAB这样的

假设$A<B$,我们把AB看成一次操作,模拟一下字符串有哪些变化。

我们发现这次操作在原序列不变的基础上,把最前面(B-A)长度的串扔到了字符串末尾。

然后若干次循环以后,这个段又回到最开始,这就类似于更相减损法,所以这个操作的最小移动单元是gcd(n,B-A)。

我们枚举每一个长度为k*(B-A)的前缀移动到末尾之后,是否与t相同就行了。

这时候我们注意,还有一个操作是BABABAB,它是以B开头的,所以我们先进行一次B,然后就变成跟第一个操作一样的处理方法了。

查看代码
 #include <bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int N = 5e5 + 1009;
const int mod = 257;
char s[N], t[N], tmp[N];
ull hs[N], pw[N];
int a, b, c, n;
ull ask(int l, int r) {
    return hs[l] - hs[r + 1] * pw[r - l + 1];
}
ull get(int pos) {
    return ask(pos, n) + ask(1, pos - 1) * pw[n - pos + 1];
}
int check() {
    ull tar = 0;
    pw[0] = 1;
    for(int i = 1; i <= n; i++) pw[i] = pw[i - 1] * mod;
    for(int i = n; i >= 1; i--) {
        tar = tar * mod + t[i];
    }
    hs[n + 1] = 0;
    for(int i = n; i >= 1; i--) {
        hs[i] = hs[i + 1] * mod + s[i];
    }
    for(int i = 1; i <= n; i += a) {
        if(tar == get(i + a)) {
            return 1;
        }
    }
    return 0;
}
void work() {
    cin >> s + 1 >> t + 1;
    cin >> a >> b;
    n = strlen(s + 1);
    c = a;
    a = max(a, b) - min(a, b);
    a = __gcd(a, n);
    if(check()) {
        cout << "yes" << endl;
        return ;
    }
    for(int i = 1; i <= n; i++) tmp[i] = s[i];
    int cnt = 0;
    for(int i = c; i >= 1; i--) s[++cnt] = tmp[i];
    for(int i = n; i > c ; i--) s[++cnt] = tmp[i];
    if(check())
        cout << "yes" << endl;
    else cout << "no" << endl;
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int Case; cin >> Case;
    while(Case--) work();
    return 0;
}

标签:Provincial,17th,chose,tx,ty,int,题解,--,push
来源: https://www.cnblogs.com/onglublog/p/16476450.html