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