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 K−1 个数也填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 aij≤0 的地方),也可以消耗 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;
}