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

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 最小异或和

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

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

#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 收集

思路:同一颜色的球应该以什么顺序收集。假设有 k k k个颜色为 i i i的球,它们的坐标为 x 1 , x 2 , x 3 , . . . , x k x_1, x_2, x_3, ..., x_k x1,x2,x3,...,xk(其中 x 1 < x 2 < x 3 < . . . < x k x_1 < x_2 < x_3 < ... < x_k x1<x2<x3<...<xk)。

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

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

l d p [ i ] 和 r d p [ i ] ldp[i]和rdp[i] ldp[i]rdp[i]表示完成收集所有颜色为 i i i的球所需的最少时间,其中 l d p ldp ldp表示位于该颜色为 i i i的球的最左边( x 1 x_1 x1), r d p rdp rdp表示位于最右边( x k x_k xk)。(我们假设不存在没有着色的颜色,即所有颜色都至少有一个球)

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

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

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

#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)的顶点的最短距离,对所有 i i i;对于奇数长度的回文串,只需检查对应于 ( 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;
}
  • 20
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值