第十三届重庆市大学生程序设计竞赛题解(部分)
第十三届重庆市大学生程序设计竞赛题解(部分)
by jiejiejiang
A 题目背景是 GPT 给的
题意
给你一个长度为 \(n\) 的数组,你第 \(i\) 轮操作可以让数组中每一个数与这个数的后面(循环)第 \(i\) 个数进行或运算,问:通过几轮操作可以使所有数字相等?
题解
通过观察数据范围可知,如果暴力的话,时间复杂度为 \(O(n^2)\) ,会超时,因此我们不能这样做。
经验丰富的同学很容易想到:我们需要拆位计算,找出每一段 \(0\) 的数量,计算最大轮数,然后取 \(Max\) 值(很显然如果全部都是 \(0\) 可以不管)。
那么拆位计算之后,我们如何计算需要经过多少轮呢?我们手动模拟一下。
假设现在是这么一个情况:
未开始:\(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1\)
第一轮:\(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1\) ;由于所有数只会和后面的第一个数进行或运算,所以只有一个 \(0\) 变成 \(1\)
第二轮:\(0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1\) ;由于所有数只会和后面的第二个数进行或运算,所以只有两个 \(0\) 变成 \(1\)
第三轮:\(0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1\) ;由于所有数只会和后面的第三个数进行或运算,所以只有三个 \(0\) 变成 \(1\)
以此类推,由此得知:
(1)不管原数组的 \(0\) 后面有多少个 \(1\) ,和前面的 \(0\) 进行或运算的,只有最前面那个 \(1\) ,其他的 \(1\) 没有影响
(2) 假设一段 \(0\) ,有 \(n\) 个元素,那么他第一轮最多有 \(1\) 个 \(0\) 变成 \(1\) ,第二轮最多有 \(2\) 个 \(0\) 变成 \(1\) ...... 第 \(k\) 轮最多有 \(k\) 个 \(0\) 变成 \(1\) ,直到全部变成 \(1\)。那么这一段我们计算有多少轮的方法,很容易就可以想到用二分去做,直接使用等差数列求和公式即可。
代码
#include <bits/stdc++.h>
#define int long long
using vi = std::vector<int>;
using vii = std::vector<vi>;
void solve() {
int n;
std::cin >> n;
vi a(n);
for(int i = 0 ; i < n ; i ++) {
std::cin >> a[i];
}
vii abit(n*2,vi(32,0));
for(int i = 0 ; i < n ; i ++) {
int tmp = a[i];
for(int j = 0 ; j < n && tmp; j ++) {
int b = tmp&1;
tmp >>= 1;
abit[i][j] = b;
abit[i+n][j] = b;
}
}
int max = 0;
for(int j = 0 ; j < 32 ; j ++) {
int mem = 1;
for(int i = 1 ; i < n*2 ; i ++) {
if(abit[i][j] != abit[i-1][j]) {
if(abit[i][j] == 1) {
int l = 1 , r = 1e5;
while(l < r) {
int mid = l + r >> 1;
int res = (1 + mid) * mid / 2;
if(res < mem) l = mid + 1;
else r = mid;
}
max = std::max(max,l);
}
mem = 0;
}
mem += 1;
}
}
std::cout << max << "\n";
}
signed main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
int _ = 1;
std::cin >> _;
while(_--) {
solve();
}
return 0;
}
B 列车
题意
已知有 \(n\) 个站点, \(m\) 辆列车,第 \(i\) 辆列车从 \(l_i\) 个站点开向第 \(r_i\) 个站点,最多上 \(c_i\) 人,只能在 \(l_i\) 站点上车,可以在途中下车,每辆列车只开一次,发车时间任意。问:若 \(1\) 站点有无穷多人,最多有多少人能到达 \(n\) 站点?
题解
按照官方题解,
证明:\(L_i\) 向 \(R_i\) 连边,容量为 \(c_i\) ,但由于可以提前下车,\(i+1\) 向 \(i\) 连边,容量为无限大,给与反悔提前下车的机会。根据最大流最小割定理,可以证明最多的人数就是最小割。
很容易看得出来这是网络流,但是由于数据范围很大,所以不可能直接跑一遍网络流,那么它可能是有其特殊性,不需要跑网络流也能得到答案。
我们想一下,所有列车都是从小站点跑到大站点的,而且发车时间任意,我们的脑海里可以有一个多辆列车并行的场景。
多条小路,汇到大路里面,那么这段路就是所有经过的列车的和,其实就是 区间加 ,可以使用差分维护。 ——Catbiscuit
使用差分维护,可是观察得知,数据范围太大,因此我们需要先进行离散化。
离散化之后进行差分,再从左到右计算能到达该车站的人数(取 \(Min\) 值),扫一遍即得到答案。
注意! 如果你离散化之后发现 \(\text{Wa}\) 了,那么就是你离散化的方法不对, 请测试一下样例:
1
3 3
1 1 100
2 2 100
3 3 100
如果得到的答案是 \(100\) ,那么就是离散化不对了,正确答案是 \(0\) 。
如果注意力足够敏锐,能够发现。如果直接进行普通的离散化,那么没有对车站和车站之间有效地隔开。那直接让车站和车站之间离散化后,\(\text{idx}\) 差值为 \(2\) 即可。
代码
#include <bits/stdc++.h>
#define int long long
using vi = std::vector<int>;
using vii = std::vector<vi>;
const int INF = 1e18;
void solve() {
int n,m;
std::cin >> n >> m;
std::set<int> s;
vii a(m,vi(3));
for(auto & i : a) {
for(int j = 0 ; j < 3 ; j ++) {
std::cin >> i[j];
if(j < 2) {
s.insert(i[j]);
}
}
}
s.insert(1);
s.insert(n);
int idx = 0;
std::map<int,int> mp;
for(auto i : s) {
mp[i] = idx;
idx += 2;
}
vi cha(idx+2,0);
for(auto i : a) {
cha[mp[i[0]]] += i[2];
cha[mp[i[1]]+1] -= i[2];
}
int ans = INF;
int now = 0;
int endd = mp[n];
for(int i = 0 ; i <= endd ; i ++) {
now += cha[i];
ans = std::min(ans,now);
}
std::cout << ans << "\n";
}
signed main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int t = 1;
std::cin >> t;
while(t--) {
solve();
}
return 0;
}
代码2
由于 区间加 ,因此我们也可以用线段树维护这个数据结构。
以下代码 \(\text{by Catbiscuit}\)
#include <bits/stdc++.h>
using namespace std;
using i128 = __int128;
using LL = long long;
using loli = long long;
using i0721 = unsigned long long;
using ULL = unsigned long long;
using PLL = array<LL, 2>;
using ldb = long double;
#define all(x) x.begin(), x.end()
#define all1(x) x.begin() + 1, x.end()
#define endl "\n"
#define qwq ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
LL read()
{
LL sum = 0, fl = 1;
int ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-')
fl = -1;
for (; isdigit(ch); ch = getchar())
sum = sum * 10 + ch - '0';
return fl * sum;
}
void write(LL x)
{
static int sta[35];
int top = 0;
do
{
sta[top++] = x % 10, x /= 10;
} while (x);
while (top)
putchar(sta[--top] + 48); // 48 是 '0'
putchar(' ');
}
constexpr int P = 998244353;
LL qpow(LL a, LL b)
{
LL res = 1;
while (b)
{
if (b & 1)
res = res * a % P;
a = a * a % P;
b >>= 1;
}
return res % P;
}
LL inv(LL x)
{
return qpow(x, P - 2);
}
constexpr double esp = 1e-12;
const int N = 1000005;
struct node
{
LL l, r, minn, tag;
};
#define L tr[u].l
#define R tr[u].r
#define lc (u << 1)
#define rc (u << 1 | 1)
#define mid ((L + R) >> 1)
node tr[N * 4 + 10];
void build(LL u, LL l, LL r)
{
tr[u] = {l, r, 0, 0};
if (l == r)
return;
build(lc, l, mid);
build(rc, mid + 1, r);
}
void make(LL u, LL tag)
{
tr[u].tag += tag;
tr[u].minn += tag;
}
void pushdown(LL u)
{
make(lc, tr[u].tag);
make(rc, tr[u].tag);
tr[u].tag = 0;
}
void pushup(LL u)
{
tr[u].minn = min(tr[lc].minn, tr[rc].minn);
}
void update(LL u, LL l, LL r, LL x)
{
if (l <= L && R <= r)
{
make(u, x);
return;
}
pushdown(u);
if (mid >= l)
update(lc, l, r, x);
if (mid < r)
update(rc, l, r, x);
pushup(u);
}
LL query(LL u, LL l, LL r)
{
if (l <= L && R <= r)
return tr[u].minn;
LL minn = numeric_limits<LL>::max();
pushdown(u);
if (mid >= l)
minn = min(minn, query(lc, l, r));
if (mid < r)
minn = min(minn, query(rc, l, r));
return minn;
}
void solve()
{
LL n = read(), m = read();
vector<LL> l(m + 1), r(m + 1), c(m + 1);
vector<LL> v;
for (int i = 1; i <= m; i++)
l[i] = read(), r[i] = read(), c[i] = read(), v.push_back(l[i]), v.push_back(r[i]);
v.push_back(n);
v.push_back(1);
sort(all(v));
v.erase(unique(all(v)), v.end());
auto id = [&](LL x)
{
return lower_bound(all(v), x) - v.begin() + 1; // 1idx
};
build(1, 1, (LL)v.size() + 10);
for (int i = 1; i <= m; i++)
{
if (l[i] == r[i])
continue;
update(1, id(l[i]), id(r[i]) - 1, c[i]);
}
printf("%lld\n", query(1, 1, (LL)v.size() - 1));
}
int main()
{
LL t = read();
while (t--)
solve();
return 0;
}
C 区域划分
题意
把一块 \(n \times n\) 的区域分成 \(n\) 种区域(一种区域之间可以不连通),对他们涂色:同种区域颜色相同,相邻区域颜色不同。问:给出任意一种使用最多种颜色的涂色情况。
题解
显而易见,这是一道构造题。因此我只能给出我的构造
构造1(官方题解)
由于相邻区域颜色不同,要使颜色最多,那么我们构造任意两种区域都存在相邻。
如何构造呢?只需要构造所有相邻两个格子的颜色不同的情况,一共是 \(\frac{n \times (n-1)}{2}\) 种情况,那么一共就有 \(n \times (n-1)\) 个格子,完全可以塞进大小为 \(n \times n\) 的区域内
构造2
已知这块区域内一共有 \(n\) 行, \(n\) 列,那么我们可以构造:
首先第一行把 \(1\) 至 \(n\) 一次排开
第 \(i\) 行第 \(j\) 列的数字,是其上一行相同列的数字往后第 \(i-1\) 个数字(循环)
比如,假设 \(n = 4\) ,第一行中的数字 \(1\) ,下面是 \(2\) ;第二行的 \(1\) ,下面是 \(3\) ,第三行的 \(1\) ,下面是 \(4\) 。刚好符合要求。
代码(构造1)
#include <bits/stdc++.h>
#define int long long
using vi = std::vector<int>;
using vii = std::vector<vi>;
void solve() {
int n;
std::cin >> n;
vii ans(n,vi(n,1));
int x = 0 , y = 0;
for(int i = 1 ; i <= n ; i ++) {
for(int j = i + 1 ; j <= n ; j ++) {
ans[x][y] = i;
ans[x][y+1] = j;
if(y + 2 + 1 >= n) {
x ++;
y = 0;
} else {
y += 2;
}
}
}
for(int i = 0 ; i < n ; i ++) {
for(int j = 0 ; j < n ; j ++) {
std::cout << ans[i][j] << " ";
}
std::cout << "\n";
}
}
signed main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int t = 1;
std::cin >> t;
while(t--) {
solve();
}
return 0;
}
代码(构造2)
#include <bits/stdc++.h>
#define int long long
void solve() {
int n;
std::cin >> n;
int idx = 0;
for(int i = 1 ; i <= n ; i ++) {
for(int j = 1 ; j <= n ;j ++) {
std::cout << (j + idx) % n + 1 << " ";
}
std::cout << "\n";
idx += i;
}
}
signed main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int t = 1;
std::cin >> t;
while(t--) {
solve();
}
return 0;
}
F 赢麻了
题意
给你三个数 \(a,b,c\) 。
- 如果 \(a > b\) ,输出 \(\text{Win}\) 。
- 如果 \(c > b\) ,输出 \(\text{WIN}\) 。
- 其他情况 ,输出 \(\text{nowin}\) 。
题解
见题意
代码
#include <bits/stdc++.h>
#define int long long
void solve() {
int a,b,c;
std::cin >> a >> b >> c;
if(a > b) {
std::cout << "Win\n";
return;
}
if(c > b) {
std::cout << "WIN\n";
return;
}
std::cout << "nowin\n";
}
signed main() {
std::ios::sync_with_stdio(0);
std::cin.tie(0);
std::cout.tie(0);
int _ = 1;
std::cin >> _;
while(_--) {
solve();
}
return 0;
}
H 连接
题意
在区域内有 \(n\) 个在上边界周围的小球(有部分小球紧贴上边界),有 \(n\) 个紧贴下边界的小球,上下边界的小球小球均标了号且一一对应,你需要给他们对应的小球连线:线与线之间不能相交,且线不能跑出上下边界。现在给你各个小球之间的相对位置,问:能否实现?
题解
其实还是很好 \(\text{guess}\) 的这题,由于不是紧贴上边界的小球可以随意移动,所以只需要判断今天上边界的小球和对应的位于下边界小球他们序号的排序是否一样即可(若不一样就会相交)。
由于实现的时候有挺多细节的,所以可能会 \(\text{Wa}\) 好几发
代码
#include <bits/stdc++.h>
#define int long long
using vi = std::vector<int>;
void solve() {
int n;
std::cin >> n;
vi up(n),down(n),isa(n),a(n);
for(int i = 0 ; i < n ; i ++) {
std::cin >> up[i];
up[i] --;
}
for(int i = 0 ; i < n ; i ++) {
std::cin >> down[i];
down[i] --;
a[down[i]] = i;
}
for(int i = 0 ; i < n ; i ++) {
std::cin >> isa[i];
}
int max = -1;
for(int i = 0 ; i < n ; i ++) {
int poi = up[i];
if(isa[i] == 1) {
if(max > a[poi]) {
std::cout << "No\n";
return;
}
max = std::max(a[poi],max);
}
}
std::cout << "Yes\n";
}
signed main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int t = 1;
std::cin >> t;
while(t--) {
solve();
}
return 0;
}
J RGB 树
题意
有一棵有 \(n\) 个节点的无根树,树上的每一个节点都涂有 \(\text{R,B,G}\) 三种颜色之一。
你现在可以把树上任意节点涂成白色( \(\text{R,B,G}\) 之外的一种颜色),使以下三点成立:
-
任意两个非红色结点的简单路径上没有红色结点。
-
任意两个非蓝色结点的简单路径上没有蓝色结点。
-
任意两个非绿色结点的简单路径上没有绿色结点。
题解
首先,很容易就能想到,叶子结点肯定不用删除。
如果叶子结点往里走,这个结点和叶子结点颜色相同,同时这个结点最多只和一个和他颜色不一样的结点相邻,那么也可以把这个节点删掉。
就这个思路去做就可以了,可以 对所有节点跑一遍 \(\text{bfs}\) 或者 分别对三种颜色跑 \(\text{dfs}\) 或者 跑一遍 \(\text{dfs}\) (实现起来可能有点复杂)
代码1( \(\text{bfs}\) )
#include <bits/stdc++.h>
#define int long long
using vi = std::vector<int>;
using vii = std::vector<vi>;
void solve() {
int n;
std::cin >> n;
std::string col;
std::cin >> col;
col = " " + col;
vii g(n+1);
vi vis(n+1,1);
vi d(n+1,0);
for(int i = 1 ; i < n ; i ++) {
int a,b;
std::cin >> a >> b;
d[a] ++;
d[b]++;
g[a].push_back(b);
g[b].push_back(a);
}
if(n == 1) {
std::cout << 0 << "\n";
return;
}
std::map<int,int> mpc;
mpc['R'] = 0;
mpc['B'] = 1;
mpc['G'] = 2;
int sum = n;
std::queue<int> q;
for(int i = 1 ; i <= n ; i ++) {
if(d[i] == 1) {
q.push({i});
vis[i] = 0;
}
}
while(!q.empty()) {
sum --;
int poi = q.front();
q.pop();
for(auto i : g[poi]) {
if(!vis[i]) continue;
d[i] --;
if(d[i] == 1) {
if(col[i] == col[poi]) {
bool f = true;
for(auto j : g[i]) {
if(!vis[j] && col[j] != col[i]) {
f = false;
}
}
if(f) {
q.push(i);
vis[i] = 0;
}
}
}
}
}
std::cout << sum << "\n";
}
signed main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int t = 1;
// std::cin >> t;
while(t--) {
solve();
}
return 0;
}
代码2(三个 \(\text{dfs}\) )
#include <bits/stdc++.h>
#define int long long
using vi = std::vector<int>;
using vii = std::vector<vi>;
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
s = " " + s;
vii g(n+1);
for(int i = 1 ; i < n ; i ++) {
int a,b;
std::cin >> a >> b;
g[a].push_back(b);
g[b].push_back(a);
}
int ans = n;
vi vis(n+1,0);
auto dfs = [&](auto && dfs, int poi,int biao)->bool {
vis[poi] = 1;
bool f = (s[poi] == biao);
for(auto i : g[poi]) {
if(vis[i]) continue;
f &= dfs(dfs,i,biao);
}
if(f) ans --;
vis[poi] = 0;
return f;
};
auto check = [&](int color) {
vis.resize(n+1,0);
for(int i = 1 ; i <= n ; i ++) {
if(s[i] != color) {
dfs(dfs,i,color);
return;
}
}
ans = 0;
};
check('R');
check('B');
check('G');
std::cout << ans << "\n";
}
signed main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int t = 1;
// std::cin >> t;
while(t--) {
solve();
}
return 0;
}
代码3(一个 \(\text{dfs}\) )
\(\text{by Rescells}\)
#include <bits/stdc++.h>
#define int long long
using vi = std::vector<int>;
using vii = std::vector<vi>;
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
s = '0' + s;
vii g(n + 1, vi());
vi du(n+1,0);
for (int i = 1; i <= n - 1; i++) {
int a, b;
std::cin >> a >> b;
g[a].push_back(b);
g[b].push_back(a);
du[a] ++;
du[b] ++;
}
std::vector<bool> vis1(n + 1);
std::queue<int> q;
auto dfs = [&](auto && dfs, int u, int fa) -> void {
vis1[u] = 1;
for (auto j : g[u]) {
if (j == fa || s[j] != s[u] || vis1[j]) continue;
du[j] --;
du[u] --;
if(du[j] == 1) {
dfs(dfs, j, u);
}
}
};
for (int i = 1; i <= n; i++) {
if (g[i].size() == 1) {
q.push(i);
}
}
while (q.size()) {
auto t = q.front();
q.pop();
dfs(dfs, t, -1);
}
int ans = n;
for (int i = 1; i <= n; i++) {
if (vis1[i]) {
ans--;
}
}
std::cout << ans << "\n";
}
signed main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int t = 1;
// std::cin >> t;
while(t--) {
solve();
}
return 0;
}
L 栈与重复
题意
给你一个栈,你可以对这个栈进行 \(3\) 种操作:
- \(\text{Push x}\) :将非负整数 \(x\) 压入栈。
- \(\text{Pop}\) :栈顶元素弹出
- \(\text{Repeat}\):重复执行一遍此前的所有语句(不包含本语句)(包括 \(\text{Repeat}\) )。
问:输出每一步操作后栈中元素的和(对 \(998224353\) 取模)
题解
首先,我们很容易想到直接模拟。
但是如果直接模拟的话,每一个 \(\text{Repeat}\) 相当于一次递归,因此,实际上我们可以把 \(\text{Repeat}\) 看成把当前的栈复制一遍
但是,如果直接复制一遍接在后面,又会 \(\text{Mle}\) ,因此要解决元素过多的问题。
如果你的注意力足够敏锐,会发现操作最多也就 \(\text{n}\) 次,因此最多 \(\text{Pop n}\) 次。
因此,当元素不足 \(\text{n}\) 时,便执行复制操作;反之,不需要执行复制操作,直接修改总和即可。
其他操作直接处理即可。
代码
#include <bits/stdc++.h>
#define int long long
using vi = std::vector<int>;
const int mod = 998244353;
void solve() {
int n;
std::cin >> n;
int sum = 0;
vi a(1e6 + 10);
int idx = 0;
while(n--) {
std::string op;
std::cin >> op;
if(op == "Push") {
int in;
std::cin >> in;
a[idx] = in;
idx ++;
sum = (sum + in) % mod;
} else if(op == "Pop") {
idx --;
sum = (sum - a[idx] + mod) % mod;
} else {
if(idx <= n) {
int cel = idx;
for(int i = 0 ; i < cel ; i ++) {
a[idx] = a[i];
idx ++;
}
}
sum = sum * 2 % mod;
}
std::cout << sum << "\n";
}
}
signed main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
int t = 1;
// std::cin >> t;
while(t--) {
solve();
}
return 0;
}
posted on 2025-05-15 19:09 Jiejiejiang 阅读(280) 评论(0) 收藏 举报
浙公网安备 33010602011771号