2021 BNU Winter Training 9 (2020CCPC东北四省赛)

2021 BNU Winter Training 9 (The 14th Chinese Northeast Collegiate Programming Contest)

训练网址

A. Micro Structure Thread

  • 这道题题解都搜不到啊,但是听说可以转化为最小生成树

B. Team

  • 网络流

C. Liner vectors

  • 要求构造一个 N * N 的矩阵,矩阵的每个元素都由0或1构成。矩阵的每一行元素之和都是K。要求这个矩阵的任意一个行向量不能被其他若干向量异或表示出来。
  • 线性基,其实就是要求这个矩阵的秩为N
  • 观察样例,其实有一个很简单的方法(N = 5, K = 3)
    ( 0 0 1 1 1 0 1 0 1 1 0 1 1 0 1 0 1 1 1 0 1 0 0 1 1 ) \begin{pmatrix} 0 & 0 & 1 & 1 & 1 \\ 0 & 1 & 0 & 1 & 1 \\ 0 & 1 & 1 & 0 & 1 \\ 0 & 1 & 1 & 1 & 0 \\ 1 & 0 & 0 & 1 & 1 \end{pmatrix} 0000101110101101101111101
  • 你会发现,前 K + 1 行在右侧构成了一个 ( K + 1 ) × ( K + 1 ) (K + 1) \times(K + 1) (K+1)×(K+1) 的矩阵,类似于对角矩阵,不过对角线是0,其他元素是1. 然后剩下 K + 2 ~ N 行,每行最前面只填1个1. 最后 K − 1 K - 1 K1 个数也填1. 这样子,显然,当K为奇数时,前 K + 1 行是线性无关的(每行每列都有奇数个1,异或起来一定不是0)。 K + 2 K + 2 K+2 ~ N N N 行是阶梯型矩阵反过来了,也是线性无关的。而前后两部分也是线性无关的。
  • 至于不合法情况,显然 N == K 时是不合法的。然后若 K 为偶数,那么全部异或起来一定是0,也不合法。
  • 这个构造方法是真的巧妙啊。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef unsigned long long ll;
ll a[70];
int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		int N, K;
		scanf("%d%d", &N, &K);
		if (N == 1) printf("1\n");
		else if (N == K || K % 2 == 0) printf("-1\n");
		else {
			for (int i = 0; i < K + 1; i++) {
				a[i] = (1LL << K + 1) - 1;
				a[i] ^= (1LL << (K - i));
			}
			for (int i = K + 1; i < N; i++) {
				a[i] = (1LL << (K - 1)) - 1;
				a[i] ^= (1LL << i);
			}
			for (int i = 0; i < N; i++) {
				printf("%llu%c", a[i], i + 1 == N ? '\n' : ' ');
			}
		}
	}
	return 0;
}

D. PepperLa’s String

  • 题意:给字符串可以删去一个字符,可以把连续的字符替换成字符加十六进制数,求压缩后的字符串最短前提下,压缩后字符串的字典序最小。
  • 分类讨论的情况很多,容易漏。

推荐题解

#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
const int maxn = 1000010;
char str[maxn], ch[maxn];
int tot[maxn], n, m;
string dec_to_hex(int x) {
	//注意进制转换最好写成 do-while 的形式,用while的话可能会让无法转换0.
	string res;
	do {
		int xx = x % 16;
		res += (xx >= 10) ? (xx - 10 + 'A') : (xx + '0');
		x /= 16;
	} while (x);
	reverse(res.begin(), res.end());
	return res;
}
void solve() {
	n = strlen(str);
	m = 0;
	//如果只有一个字符,那么什么也不输出
	if (n == 1) return;
	//先统计有多少个连续字母
	for (int i = 0, cnt; i < n; i++) {
		if (i == 0 || str[i] != str[i - 1]) {
			cnt = 1;
		}
		else cnt++;
		if (i == n - 1 || str[i] != str[i + 1]) {
			//每一段相同的字母信息存进ch和tot里面
			ch[m] = str[i];
			tot[m] = cnt;
			m++;
		}
		
	}
	//del_pos 表示要删除字母的位置。del_pos = 0 表示字符串长度不能减小
	/*
	三种字符串长度会变小的情况,贪心策略如下
	(1)只有1个字符,删去之后为空。这时若 s[i] > s[i + 1] 就选择它 (break),否则就往后继续找
	(2)两个字符,删掉变成一个字符,由于ASCII: 数字 < 字母,因此只有它是最后一个字母时才选择
	(3)十六进制下,100... 减一变成 FF... 因此只有是最后一个字母时才选择它
	*/
	int del_pos = 0;
	for (int i = 0; i < m; i++) {
		if (tot[i] == 1) {
			del_pos = i;
			if (i == m - 1 || ch[i] > ch[i + 1]) break;
		}
		else if (tot[i] == 2 || dec_to_hex(tot[i]).size() > dec_to_hex(tot[i] - 1).size()) {
			del_pos = i;
		}
	}
	//for (int i = 0; i < m; i++) {
	//	printf("%c %d %s\n", ch[i], tot[i], dec_to_hex(tot[i]).c_str());
	//}
	tot[del_pos]--;
	for (int i = 0; i < m; i++) {
		if (tot[i] == 0) continue;
		cout << ch[i];
		if(tot[i] > 1) cout << dec_to_hex(tot[i]);
	}
	cout << endl;
}
int main() {
	while (cin >> str) {
		solve();
	}
	return 0;
}
/*
aabbbbbbbbbbbbbbbbb
aabbbbbbbbbbbbbbbb
aaacccccccccc
aaabaaa
abcdef
*/

E. PepperLa’s Cram School

F. PepperLa’s Boast

  • ( 1 , 1 ) (1, 1) (1,1) 走到 ( N , M ) (N, M) (N,M), 一步可以走三个方向,每次可以走一步(不可以走到 a i j ≤ 0 a_{ij} \le 0 aij0 的地方),也可以消耗 U 走最多 K 步(可以少于K步),求到终点最大值。
  • 只有 a ( i , j ) > 0 a(i, j) > 0 a(i,j)>0 才更新 f ( i , j ) f(i, j) f(i,j)
    在这里插入图片描述
    在这里插入图片描述
#include<iostream>
#include<cstring>
#include<algorithm>
#include<deque>
using namespace std;
const int maxn = 1010;
typedef long long ll;
ll a[maxn][maxn], f[maxn][maxn];
ll N, M, K, U;
deque<int> col[maxn];
typedef pair<ll, int> P;
void update(ll& x, const ll& y) {
	if (x < y) x = y;
}
void solve() {
	
	for (int j = 1; j <= M; j++) col[j].clear();
	deque<P> row;
	f[1][1] = a[1][1];
	for (int i = 1; i <= N; i++) {
		row.clear();
		for (int j = 1; j <= M; j++) {
			while (col[j].size() && col[j].front() < i - K) col[j].pop_front();
			while (row.size() && row.front().second < j - K) row.pop_front();
			if (a[i][j] > 0) {
				// 走一步,不憋气
				if (f[i - 1][j] != -1) update(f[i][j], f[i - 1][j] + a[i][j]);
				if (f[i][j - 1] != -1) update(f[i][j], f[i][j - 1] + a[i][j]);
				if (f[i - 1][j - 1] != -1) update(f[i][j], f[i - 1][j - 1] + a[i][j]);
				// 从某一个远地方憋着气到这个地方
				// 首先更新当前小矩阵的最大值
				if (col[j].size()) {
					while (row.size() && row.back().first <= f[col[j].front()][j]) row.pop_back();
					row.push_back({ f[col[j].front()][j], j });
				}
				//然后更新憋气到这个地方
				if (row.size()) {
					update(f[i][j], row.front().first + a[i][j] - U);
				}
				//因为要把 f[i][j] 更新这个小矩阵。因此如果上面更新了,要把更新的col弄出来
				if (col[j].size()) row.pop_back();
			}
			//更新小矩阵的值
			//先更新列滑动窗口
			if (f[i][j] >= U) {
				while (col[j].size() && f[col[j].back()][j] <= f[i][j]) col[j].pop_back();
				col[j].push_back(i);
			}
			//再更新行滑动窗口
			if (col[j].size()) {
				while (row.size() && row.back().first <= f[col[j].front()][j]) row.pop_back();
				row.push_back({f[col[j].front()][j], j});
			}
		}
	}
	cout << f[N][M] << endl;
}
int main() {
	memset(f, -1, sizeof f);
	while (cin >> N >> M >> K >> U) {
		for (int i = 1; i <= N; i++) {
			for (int j = 1; j <= M; j++) {
				scanf("%lld", &a[i][j]);
				f[i][j] = -1;
			}
		}
		solve();
	}
	
}

G. PepperLa’s Express

  • 题意:题意是有一些快递点和一些用户,一个用户的代价是到最近快递点的曼哈顿距离。要求增加一个快递点最小化用户代价的最大值。题目中的 “minimal dilivery time” 指的是求最小化的最大时间,而不是求最小时间。

  • 首先,我们可以根据 F l o u d   F i l l Floud\ Fill Floud Fill 模型(建立虚拟源点)求出每一个user到所有dilivery station的最近距离。

  • 我们设想的是,二分答案。然后大于二分结果的,我们看能否将所有的点都小于等于答案。二分的原因在于,我们可以在 O ( 1 ) O(1) O(1) 的时间内求出某个点到某些点的最大值。因此,我们要找到哪些点不满足条件。
    在这里插入图片描述

  • 曼哈顿距离可以拆成这个样子,因为我们只关注所有点到 ( x i , y i , z i ) (x_i, y_i, z_i) (xi,yi,zi) 的最大值。因此要取两次 m a x max max,因此,我们只需找到后面 ( ± x j , ± y j , ± z j ) (\pm x_j,\pm y_j,\pm z_j) (±xj,±yj,±zj) 每一项的最大值即可,就是这个样子
    在这里插入图片描述
    代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn = 110;
char mz[maxn][maxn][maxn];
int dx[] = { 1, -1, 0, 0, 0, 0 }, dy[] = { 0, 0, 1, -1, 0, 0 }, dz[] = { 0, 0, 0, 0, 1, -1 };
int op[] = { 1, -1 };
int dist[maxn][maxn][maxn];
int X, Y, Z;
int mmax[8];
struct P {
	int z, x, y;
};
void bfs() {
	queue<P> que;
	for (int z = 1; z <= Z; z++) {
		for (int x = 1; x <= X; x++) {
			for (int y = 1; y <= Y; y++) {
				if (mz[z][x][y] == '@') {
					dist[z][x][y] = 0;
					que.push({ z, x, y });
				}
				else dist[z][x][y] = -1;
			}
		}
	}
	while (que.size()) {
		auto u = que.front(); que.pop();
		int z = u.z, x = u.x, y = u.y;
		for (int i = 0; i < 6; i++) {
			int zz = z + dz[i], xx = x + dx[i], yy = y + dy[i];
			if (zz < 1 || zz > Z || xx < 1 || xx > X || yy < 1 || yy > Y) continue;
			if (dist[zz][xx][yy] >= 0) continue;
			dist[zz][xx][yy] = dist[z][x][y] + 1;
			que.push({ zz, xx, yy });
		}
	}
}
bool check(int m) {
	memset(mmax, -0x3f, sizeof mmax);
	int cnt = 0;
	for (int z = 1; z <= Z; z++) {
		for (int x = 1; x <= X; x++) {
			for (int y = 1; y <= Y; y++) {
				// 要选出 距离大于m 的 用户
				if (dist[z][x][y] <= m || mz[z][x][y] != '*') continue;
				cnt++;
				for (int i = 0; i < 8; i++) {
					mmax[i] = max(mmax[i], -(op[i & 1] * z + op[(i >> 1) & 1] * x + op[(i >> 2) & 1] * y));
				}
			}
		}
	}
	//容易忽视这个特殊条件的判定
	if (cnt == 0) return true;
	for (int z = 1; z <= Z; z++) {
		for (int x = 1; x <= X; x++) {
			for (int y = 1; y <= Y; y++) {
				int res = -1;
				if (mz[z][x][y] == '.') {
					for (int i = 0; i < 8; i++) {
						res = max(res, mmax[i] + z * op[i & 1] + x * op[(i >> 1) & 1] + y * op[(i >> 2) & 1]);
					}
					if (res <= m) return true;
				}
				
			}
		}
	}
	return false;
}
void solve() {
	bfs();
	int l = 0, r = X + Y + Z;
	while (r > l) {
		int mid = (l + r) / 2;
		//小心二分别写错了呀
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	printf("%d\n", l);
}
int main() {
	while (cin >> Z >> X >> Y) {
		for (int z = 1; z <= Z; z++) {
			for (int x = 1; x <= X; x++) {
				scanf("%s", mz[z][x] + 1);
			}
		}
		solve();
		/*for (int i = 1; i <= X; i++) {
			for (int j = 1; j <= Y; j++) {
				printf("%d ", dist[1][i][j]);
			}
			printf("\n");
		}*/
	}
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值