2023-2024-1 ACM集训队每周程序设计竞赛(10)题解

文章介绍了C++代码解决五个不同编程题目:字符串处理(A、B),动态规划(C),深度/广度优先搜索(D、F),以及最短路径(E)。涉及内容包括子串查找、区间覆盖和最短异或和计算等。

A 字符串

思路:模拟题意。

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

int main() {
    ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
    string a;
    cin >> a;
    cout << a.substr(1, a.size() - 1) << a[0];
    return 0;
}


B 能看见几个?

思路:模拟题意。

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

int main() {
    ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
    int n, m, x, y;
    cin >> n >> m >> x >> y;
    x--, y--;
    vector<string>a(n);
    for (int i = 0; i < n; i++)cin >> a[i];
    int res = 0;
    for (int i = x; i >= 0 && a[i][y] == '.'; i--)res++;
    for (int i = x; i < n && a[i][y] == '.'; i++) res++;
    for (int i = y; i >= 0 && a[x][i] == '.'; i--) res++;
    for (int i = y; i < m && a[x][i] == '.'; i++) res++;
    cout << res - 3 << '\n';
    return 0;
}


C 最小异或和

思路:为了尝试将 AAA 划分为一个或多个非空段落,我们可以尝试在相邻元素之间的 N−1N-1N1 个位置中选择放置“分隔符”的所有 2(N−1)2^{(N-1)}2(N1) 种组合方式。在实现上,一种有用的方法是进行位运算的穷举搜索,其中每个是否放置分隔符的 N−1N-1N1 个选择组合被视为 N−1N-1N1 位二进制整数,并对应于一个介于 000(含)和 2(N−1)2^{(N-1)}2(N1)(不含)之间的整数。

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

int main() {
    ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    vector<int>a(n);
    for (int i = 0; i < n; i++)cin >> a[i];
    int res = 2e9;
    cout << (1 << 30) << '\n';
    for (int i = 0; i < (1 << (n - 1)); i++) {
        int tres = 0, t = 0;
        for (int j = 0; j < n; j++) {
            t |= a[j];
            if ((1 << j)&i) {
                tres ^= t;
                t = 0;
            }
        }
        tres ^= t;
        res = min(res, tres);
    }
    cout << res << '\n';
    return 0;
}

D 铺房间

思路:由于约束条件较小,其中 HW≤16HW \leq 16HW16,因此我们可以考虑穷举地搜索覆盖的方式。
从左上角的单元格开始,进行深度优先搜索,尝试放置一个方形的榻榻米垫子,或者一个向左或向下伸出的矩形榻榻米垫子;这样,我们可以穷举地搜索所有的覆盖方式。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int h, w;
int f[20][20];
int dfs(int x, int y, int a, int b) {
    if (x == h + 1 && y == 1)return 1;
    int tx = x, ty = y + 1;
    if (ty > w) {
        tx++;
        ty = 1;
    }
    if (f[x][y]) return dfs(tx, ty, a, b);
    int res = 0;
    if (b) {
        f[x][y] = 1;
        res += dfs(tx, ty, a, b - 1);
        f[x][y] = 0;
    }
    if (a && y + 1 <= w && f[x][y + 1] == 0) {
        f[x][y] = 1;
        f[x][y + 1] = 1;
        res += dfs(tx, ty, a - 1, b );
        f[x][y] = 0;
        f[x][y + 1] = 0;
    }
    if (a && x + 1 <= h && f[x + 1][y] == 0) {
        f[x][y] = 1;
        f[x + 1][y] = 1;
        res += dfs(tx, ty, a - 1, b );
        f[x][y] = 0;
        f[x + 1][y] = 0;
    }
    return res;
}
int main() {
    ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
    int a, b;
    cin >> h >> w >> a >> b;
    cout << dfs(1, 1, a, b) << '\n';

    return 0;
}


E 收集

思路:同一颜色的球应该以什么顺序收集。假设有kkk个颜色为iii的球,它们的坐标为x1,x2,x3,...,xkx_1, x_2, x_3, ..., x_kx1,x2,x3,...,xk(其中x1<x2<x3<...<xkx_1 < x_2 < x_3 < ... < x_kx1<x2<x3<...<xk)。

在这里,可以证明无论前一个颜色的球收集后的位置如何,我们可以假设最后一个收集的球要么位于x1x_1x1,要么位于xkx_kxk(在经过前一个颜色的球之后,不收集当前颜色的球是没有意义的,并且在经过x1x_1x1xkx_kxk之后,所有其他的球都已经经过)。

因此,我们可以使用以下动态规划(DP)算法解决该问题:

ldp[i]和rdp[i]ldp[i]和rdp[i]ldp[i]rdp[i]表示完成收集所有颜色为iii的球所需的最少时间,其中ldpldpldp表示位于该颜色为iii的球的最左边(x1x_1x1),rdprdprdp表示位于最右边(xkx_kxk)。(我们假设不存在没有着色的颜色,即所有颜色都至少有一个球)

具体来说,设lll为颜色为iii的球的最小坐标,rrr为最大坐标,则在收集完颜色为i−1i-1i1的球后,到达坐标lll所需的最少时间为:

  • 如果x≤rx \leq rxr:你先向右移动到rrr,然后再向左移动到lll,所需时间为(r−x)+(r−l)(r-x) + (r-l)(rx)+(rl)
  • 如果x>rx > rx>r:你只需要向左移动到lll,所需时间为r−xr-xrx

同样地,我们可以找到从收集了颜色为i−1i-1i1后的坐标xxx开始,到达坐标rrr所需的最少时间。

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

int main() {
	ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
	int n;
	cin >> n;
	vector<int>e[n + 1];
	for (int i = 0; i < n; i++) {
		int x, c;
		cin >> x >> c;
		e[c].push_back(x);
	}
	vector<int>l(n + 1, 0), r(n + 1, 0);
	vector<ll>ldp(n + 1, 0), rdp(n + 1, 0);
	for (int i = 1; i <= n; i++) {
		sort(e[i].begin(), e[i].end());
		if (e[i].size()) {
			ll tl = ldp[i - 1] + abs(e[i][e[i].size() - 1] - l[i - 1]) +
			        abs(e[i][0] - e[i][e[i].size() - 1]);
			ll tr = rdp[i - 1] + abs(e[i][e[i].size() - 1] - r[i - 1]) +
			        abs(e[i][0] - e[i][e[i].size() - 1]);
			ldp[i] = min(tl, tr);
			tl = ldp[i - 1] + abs(e[i][0] - l[i - 1]) +
			     abs(e[i][e[i].size() - 1] - e[i][0]);
			tr = rdp[i - 1] + abs(e[i][0] - r[i - 1]) +
			     abs(e[i][e[i].size() - 1] - e[i][0]);
			rdp[i] = min(tl, tr);
			l[i] = e[i][0];
			r[i] = e[i][e[i].size() - 1];
		}
		else {
			l[i] = l[i - 1];
			r[i] = r[i - 1];
			ldp[i] = ldp[i - 1];
			rdp[i] = rdp[i - 1];
		}
	}
	cout << min(ldp[n] + abs(l[n]), rdp[n] + abs(r[n]));
	return 0;
}

F 最短回文

思路:假设在每个顶点1和N上都放有一块棋子,并且你可以重复以下操作:“将每个棋子移动到其相邻的顶点,使用具有相同字符的边”——直到两个棋子最终靠近。

对于回文串的长度为偶数的情况,只需要找到使两个棋子到达相同顶点的最小移动次数;对于长度为奇数的回文串,只需要找到使两个棋子之间的距离恰好为1的最小移动次数。

剩下的工作就是从对应于(1,N)(1, N)(1,N)的顶点开始,在这个图上执行广度优先搜索(BFS),并检查偶数长度和奇数长度的回文串。(对于偶数长度的回文串,只需检查到达对应于(i,i)(i, i)(i,i)的顶点的最短距离,对所有iii;对于奇数长度的回文串,只需检查对应于(a,b)(a, b)(a,b)(b,a)(b, a)(b,a)的顶点之间的最短距离,对所有边(a,b)(a, b)(a,b))。

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

int main() {
	ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
	int n, m;
	cin >> n >> m;
	vector<vector<vector<int>>>e(n, vector<vector<int>>(26));
	vector<vector<int>>dis(n, vector<int>(n, -1));
	queue<array<int, 2>>q;
	for (int i = 0; i < n; i++) {
		q.push({i, i});
		dis[i][i] = 0;
	}
	for (int i = 0; i < m; i++) {
		int u, v;
		char c;
		cin >> u >> v >> c;
		int cc = c - 'a';
		u--, v--;
		if (u > v)swap(u, v);
		if (dis[u][v] == -1) {
			q.push({u, v});
			dis[u][v] = 1;
		}
		e[u][cc].push_back(v);
		e[v][cc].push_back(u);
	}
	while (q.size()) {
		auto [x, y] = q.front();
		q.pop();
		for (int i = 0; i < 26; i++) {
			for (auto j : e[x][i]) {
				for (auto jj : e[y][i]) {
					if (dis[min(j, jj)][max(j, jj)] == -1) {
						dis[min(j, jj)][max(j, jj)] = dis[x][y] + 2;
						q.push({min(j, jj), max(j, jj)});
					}
				}
			}
		}
	}
	cout << dis[0][n - 1];
	return 0;
}
ACM/ICPC(ACM International Collegiate Programming Contest, 国际大学生程序设计竞赛)是由国际计算机界历史悠久、颇具权威性的组织ACM(Association for Computing Machinery,国际计算机协会)主办的,世界上公认的规模最大、水平最高的国际大学生程序设计竞赛,其目的旨在使大学生运用计算机来充分展示自己分析问题和解决问题的能力。该项竞赛1970年举办至今已历29届,一直受到国际各知名大学的重视,并受到全世界各著名计算机公司的高度关注,在过去十几年中,APPLE、AT&T、MICROSOFT和IBM等世界著名信息企业分别担任了竞赛的赞助商。可以说,ACM国际大学生程序设计竞赛已成为世界各国大学生最具影响力的国际级计算机类的赛事,是广大爱好计算机编程的大学生展示才华的舞台,是著名大学计算机教育成果的直接体现,是信息企业与世界顶尖计算机人才对话的最好机会。   该项竞赛分区域预赛和国际决赛两个阶段进行,各预赛区第一名自动获得参加世界决赛的资格,世界决赛安排在每年的3~4月举行,而区域预赛安排在上一年的9~12月在各大洲举行。   ACM/ICPC的区域预赛是规模很大、范围很广的赛事。仅在2003年参加区域预赛的队伍就有来自75个国家(地区),1411所大学的3150支代表队,他们分别在127个赛场中进行比赛,以争夺全球总决赛的73个名额,其激烈程度可想而知。 2005年第30届ACM/ICPC亚洲赛区预赛共设了北京、成都、汉城、东京等11个赛站,来自亚洲各国知名高校的各个代表队进行了激烈的角逐。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值