A-Brick Wall
这道题的难点在于题意,如果光看题面不看样例和解释很难看懂。
题意为,给定一个
n
∗
m
n * m
n∗m 的矩形面积,让你用
1
∗
k
(
2
≤
k
)
1 * k (2 \leq k)
1∗k(2≤k) 的方块去填充(不能旋转),问你最多可以填入多少块。
所以解就是
n
∗
⌊
x
/
2
⌋
n * \lfloor x / 2 \rfloor
n∗⌊x/2⌋。
B-Minimize Inversions
给定两个长度为
n
n
n 的全排列,你可以进行以下操作任意次:
a
i
a_i
ai 和
a
j
a_j
aj交换的同时,交换
b
i
b_i
bi 和
b
j
b_j
bj。
问使得“两个全排列的逆序对个数之和最小”的排列是什么。
解决这个问题需要从个例的关系去推整体。
比如两个全排列为:
1
,
2
1, 2
1,2
2
,
1
2, 1
2,1
无论怎么交换,都会有1个逆序对。
再看下面的情况:
2
,
1
2, 1
2,1
2
,
1
2, 1
2,1
如果交换
a
1
a_1
a1,
a
2
a_2
a2 和
b
1
b_1
b1,
b
2
b_2
b2,逆序对的数量则会从
2
2
2 个变为
0
0
0 个。
将上面两种情况推至整体,可以知道只有在
a
i
>
a
j
a_i > a_j
ai>aj 同时
b
i
>
b
j
b_i > b_j
bi>bj 时进行交换才会使得逆序对数量减少。
那么最简单的方法就是将其中一个数组从小到大排列即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 10;
int n, m;
int a[N];
int ans[N], b;
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
cin >> b;
ans[a[i]] = b;
}
for (int i = 1; i <= n; i++)
cout << i << ' ';
cout << '\n';
for (int i = 1; i <= n; i++)
cout << ans[i] << ' ';
cout << '\n';
return;
}
int main() {
int T = 1;
cin >> T;
while (T--) {
solve();
}
return 0;
}
C-XOR-distance
给定
a
,
b
,
r
a, b, r
a,b,r,求
∣
(
a
⊕
x
)
−
(
b
⊕
x
)
∣
\left| (a \oplus x) -(b \oplus x)\right|
∣(a⊕x)−(b⊕x)∣ 的最小值,
1
≤
x
≤
r
1 \leq x \leq r
1≤x≤r 。
按位来看、并且先不看绝对值:如果
a
a
a、
b
b
b 相等,那么
x
x
x 无论取什么值都无法对结果造成影响,反之,可以通过
x
x
x 的取
1
1
1 或是
0
0
0 来影响正负。
解,从最高的不同的位看起,我们让那一位是
1
1
1 的那个数为大的数,另一个数为小的数。让这一位的
x
x
x 为
0
0
0,如果为
1
1
1 会使得
x
x
x 更大,在后续的影响中不如填
0
0
0 优。接着后续的低位
a
,
b
a, b
a,b 如果出现
0
,
1
0, 1
0,1 不同的情况,如果小的那个数为
1
1
1 则不用处理;否则,需要让
x
x
x 的那一位为
1
1
1 ,让其变为
1
1
1 ,注意处理
x
x
x 不够大的情况。
我代码写得丑,可以用数组来代替
n
,
m
n, m
n,m。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 10;
long long n, m, r;
void solve() {
cin >> n >> m >> r;
long long res = 0, now = 0;
int t = -1, idx = 0;
for (int i = 63; i >= 0; i--) {
if ((n >> i & 1) != (m >> i & 1)) {
t = i;
if (n >> i & 1) idx = 2;
else idx = 1;
break;
}
}
if (t == -1) cout << 0 << '\n';
else {
res += 1ll << t;
for (int i = t - 1; i >= 0; i--)
if ((n >> i & 1) != (m >> i & 1)){
if (idx == 1) {
if (!(n >> i & 1)) {
if (now + (1ll << i) <= r) {now += (1ll << i); res -= (1ll << i);}
else res += (1ll << i);
} else res -= (1ll << i);
} else {
if (!(m >> i & 1)) {
if (now + (1ll << i) <= r) {now += (1ll << i); res -= (1ll << i);}
else res += (1ll << i);
} else res -= (1ll << i);
}
}cout << res << '\n';
}
return;
}
int main() {
int T = 1;
cin >> T;
while (T--) {
solve();
}
return 0;
}
D-Blocking Elements
给定一个
n
n
n 个数的序列
a
a
a,让你从中选出
m
m
m (m个数任意)个数,这会将其他的数分隔开,成为一个个区间,要使得:
m
a
x
(
m
个数的和,所有区间和的最大值)
max(m个数的和,所有区间和的最大值)
max(m个数的和,所有区间和的最大值)最小
a
i
≤
1
0
9
a_i \leq 10^9
ai≤109 。
解:最大值最小化,似乎可以用二分做。
最主要的是check函数如何写,如果贪心地,区间和大于某个数就划开,会有问题,会发现此时的划分不一定是最优的,这点从样例case2可以看出。
那么我们就需要设计以第
i
i
i 个数作为最后一个划分点的状态转移,每次枚举以
i
i
i 前面的某个点作为上一个划分点,用
d
p
[
i
]
dp[i]
dp[i]来表示以
i
i
i点作为最后一个划分点的所有作为划分的数的和的最小值。
看起来是
O
(
n
2
)
O(n^2)
O(n2) 的转移,但由于具有单调性,能优化到
O
(
n
)
O(n)
O(n)。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6 + 10;
int n, m;
long long a[N], s[N], sum;
long long dp[N];
long long q[N], hh, tt;
bool check(long long mid) {
hh = 0, tt = -1;
q[++tt] = 0;
for (int i = 1; i <= n; i++) {
while (tt - hh >= 0 && s[i-1] - s[q[hh]] > mid) hh++;
dp[i] = dp[q[hh]] + a[i];
while (tt - hh >= 0 && dp[q[tt]] >= dp[i]) tt--;
q[++tt] = i;
}
for (int i = 1; i <= n; i++)
if (dp[i] <= mid && sum - s[i] <= mid) return true;
return false;
}
void solve() {
sum = 0;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
s[i] = s[i-1] + a[i];
sum += a[i];
}
long long l = 1, r = sum;
while (l < r) {
long long mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
cout << l << '\n';
return;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int T = 1;
cin >> T;
while (T--) {
solve();
}
return 0;
}