给出骑士的跳跃能力 ( x , y ) (x, y) (x,y) 以及国王和皇后的位置,问有多少个位置可以让骑士可以直接攻击到国王和皇后。
棋盘非常大 ( 1 0 8 × 1 0 8 ) (10^{8} \times 10^{8}) (108×108),因此无法枚举所有位置,所以需要转换思想,把国王的位置看作骑士所在的位置,那么此时骑士能攻击到的位置就是实际上骑士可能被放置的位置,然后再检查这些位置能否同时攻击到皇后即可。
Tips:当 x = y x = y x=y 时,骑士只能攻击四方向。
#include
typedef long long LL;
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 3e5 + 5e4;
int n, m, x_k, y_k, x_q, y_q;
void solve() {
int ans = 0;
cin >> n >> m >> x_k >> y_k >> x_q >> y_q;
int dir[8][2] = {n, m, n, -m, -n, m, -n, -m, m, n, m, -n, -m, n, -m, -n};//8方向
int len = 8;
if (n == m) len = 4;//两个跳跃能力相同时,只能4方向跳跃
for (int i = 0; i < len; i++) {
int x = x_k + dir[i][0];//此时(x, y)为枚举的骑士位置
int y = y_k + dir[i][1];
for (int j = 0; j < len; j++) {
int xx = x + dir[j][0];
int yy = y + dir[j][1];
if (xx == x_q && yy == y_q) {//检查能否攻击到皇后
ans++;
break;
}
}
}
cout << ans << endl;
}
int main() {
int Case;
cin >> Case;
while (Case--) {
solve();
}
return 0;
}
给出一个包含 n n n 个元素的数组 a a a,开始时你可以选择一个数字 a i a_i ai 并将这个数字从数组中取出,然后可以进行若干次以下操作:
问,选择 a 1 , a 2 , . . . , a n a_1, a_2, ..., a_n a1,a2,...,an 作为开始的数字,最多可以删除多少个数字。
贪心的删除数字,在选择完数字后,可以先删除所有比自己小的数字,让自己尽可能大,然后从小到大依次去删除剩余的数字,直到无法删除,此时这一段维护的区间(起点到所有比起点大的被删除的元素),能删除的数字个数是相同的,记录能被删除的数字个数(所有前面数字均可),然后以不能被删除的点作为区间新的起点,继续去删除后面的数字,直到所有元素均被删除。
可以使用结构体存储数组,记录每个元素的值以及在原数组中的下标,并对元素的值按从小到大排序。
然后模拟上述过程即可。
#include
typedef long long LL;
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 3e5 + 5e4;
struct Node{
int num, id;
bool operator < (const Node &o) const {
if (num != o.num) return num < o.num;
return id < o.id;
}
}a[N];
int n, ans[N];
void solve() {
cin >> n;
for (int i = 0; i < n; i++) {
cin >> a[i].num;
a[i].id = i;
}
int l = 0;//区间起点
sort(a, a + n);
LL sum = a[0].num;
for (int i = 1; i < n; i++) {
if (sum < a[i].num) {//无法删除当前点了
for (int j = l; j < i; j++) {//此时区间内能删除的点的数量均相同
ans[a[j].id] = i - 1;
}
l = i;//更新区间起点
sum += a[i].num;//记录前缀和
} else {
sum += a[i].num;
}
}
for (int i = l; i < n; i++) {
ans[a[i].id] = n - 1;//最后部分的数字作为起点可以删除其他所有元素
}
for (int i = 0; i < n; i++) { if (i) cout << ' ';
cout << ans[i];
}
cout << endl;
}
int main() {
int Case;
cin >> Case;
while (Case--) {
solve();
}
return 0;
}
有个包含 n n n 个正整数的数组 a a a,你可以进行以下操作 k k k 次:
问经过操作后,数组中最小的 a i a_i ai 是多少。
分以下三种情况讨论:
k ≥ 3 k \ge 3 k≥3: 前两次操作选择同一对 ( i , j ) (i, j) (i,j),那么产生的两次减法的结果是相同的,那么再使用一次操作将这两个结果相减,得到的一定为0,因此只要 3 ≤ k 3 \le k 3≤k,就必有 m i n ( a 1 , a 2 , . . . , a n + k ) = 0 min(a_1, a_2, ..., a_{n + k}) = 0 min(a1,a2,...,an+k)=0
k = 1 k = 1 k=1: 将数组排序,使用所有相邻的后一个数字减去建一个数字,记录最小的结果,然后取这个结果与原数组中的最小值比较,哪个小就是答案。
k = 2 k = 2 k=2: 取以下三种情况中的最小值
a a a 数组中的最小值
k = 1 k = 1 k=1 时获得的最小值
枚举所有 k = 1 k = 1 k=1 时的情况,将这些点作为新的点,再通过枚举的+二分的方式找到原数组 a a a 中与这个点最接近的数字(分两种情况,比查找的数字大和小),记录减法的最小结果。
#include
typedef long long LL;
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 3e5 + 5e4;
int n, m;
LL a[N];
void solve() {
cin >> n >> m;
for (int i = 0; i < n; i++) cin >> a[i];
if (m >= 3) {
cout << 0 << endl;
return;
}
sort(a, a + n);
if (m == 1) {
LL ans = a[0];
for (int i = 1; i < n; i++) {
ans = min(ans, a[i] - a[i - 1]);
}
cout << ans << endl;
} else {
LL ans = a[0];
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
ans = min(ans, a[j] - a[i]);
int pos = lower_bound(a, a + n, a[j] - a[i]) - a;
//找到第一个大于等于的数字位置,此时下标为第一个大于等于,前一个下标为最后一个小于的,判断哪个更接近
if (pos != n) ans = min(ans, a[pos] - (a[j] - a[i]));
if (pos > 0) ans = min(ans, (a[j] - a[i]) - a[pos - 1]);
}
}
cout << ans << endl;
}
}
int main() {
int Case;
cin >> Case;
while (Case--) {
solve();
}
return 0;
}
给出包含 n n n 个元素的数组 a a a 和 b b b,你可以执行若干次以下操作:
问,能否将数组 a a a 变为数组 b b b
由于操作只能将数字变大,那么当 a i > b i a_i > b_i ai>bi 时必然无解。
然后考虑 a i < b i a_i < b_i ai<bi 的情况,此时只能选择左右两边最接近且 a j = b i a_j = b_i aj=bi 的点,同时,如果在 k = i ∼ j k = i \sim j k=i∼j之间出现了 a k > b i a_k > b_i ak>bi或 b k < b i b_k < b_i bk<bi,那么也无法将 a i a_i ai修改为 b i b_i bi。
对于D1(Easy Version)
,由于数据较小,可以使用for
循环对左右两边查找距离最近且值与 b i b_i bi相同的点,只要找到的元素与 a i a_i ai之间不存在更大的元素,且这段区间内的 b j b_j bj均大于等于 b i b_i bi,那么就可以完成修改(只需检查能否修改,不需要修改到数组中,两边只要有一边能找到就可以完成修改)。
对于D2(Hard Version)
,可以使用vector
存储数字对应的下标,使用二分对最近的值相同的点,并使用RMQ,线段树
等算法对区间内 a a a数组的最大值, b b b数组的最小值进行查找,检查是否存在合法方案即可。
#include
typedef long long LL;
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 3e5 + 5e4;
int n, m, TA[N << 2], TB[N << 2], a[N], b[N];
void pushup(int x) {
TA[x] = max(TA[x << 1], TA[x << 1 | 1]);
TB[x] = min(TB[x << 1], TB[x << 1 | 1]);
}
void build(int l, int r, int x) {
if (l == r) {
TA[x] = a[l];
TB[x] = b[l];
return;
}
int mid = l + r >> 1;
build (l, mid, x << 1);
build (mid + 1, r , x << 1 | 1);
pushup(x);
}
int queryMax(int l, int r, int x, int ql, int qr) {
if (l >= ql && r <= qr) {
return TA[x];
}
int mid = l + r >> 1;
int ans = 0;
if (ql <= mid) ans = max(ans, queryMax(l, mid, x << 1, ql, qr));
if (qr > mid) ans = max(ans, queryMax(mid + 1, r , x << 1 | 1, ql, qr));
return ans;
}
int queryMin(int l, int r, int x, int ql, int qr) {
if (l >= ql && r <= qr) {
return TB[x];
}
int mid = l + r >> 1;
int ans = 1e9;
if (ql <= mid) ans = min(ans, queryMin(l, mid, x << 1, ql, qr));
if (qr > mid) ans = min(ans, queryMin(mid + 1, r , x << 1 | 1, ql, qr));
return ans;
}
vector<int> V[N];
void easy_version() {
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
cin >> b[i];
}
for (int i = 1; i <= n; i++) {
if (a[i] > b[i]) {
cout << "NO" << endl;
return;
} else if (a[i] < b[i]) {
bool flag = false;
for (int j = i - 1; j >= 1; j--) {
if (b[j] < b[i] || a[j] > b[i]) {
break;
}
if (a[j] == b[i]) {
flag = true;
break;
}
}
for (int k = i + 1; k <= n; k++) {
if (b[k] < b[i] || a[k] > b[i]) {
break;
}
if (a[k] == b[i]) {
flag = true;
break;
}
}
if (!flag) {
cout << "NO" << endl;
return;
}
}
}
cout << "YES" << endl;
}
void hard_version() {
for (int i = 1; i <= n; i++) V[i].clear();
for (int i = 1; i <= n; i++) {
cin >> a[i];
V[a[i]].push_back(i);
}
for (int i = 1; i <= n; i++) {
cin >> b[i];
}
build(1, n, 1);
for (int i = 1; i <= n; i++) {
if (a[i] != b[i]) {
if (V[b[i]].empty()) {
cout << "NO" << endl;
return;
}
int right = lower_bound(V[b[i]].begin(), V[b[i]].end(), i) - V[b[i]].begin();
int left = right - 1;
if (left >= 0 && queryMax(1, n, 1, V[b[i]][left], i) == b[i] && queryMin(1, n, 1, V[b[i]][left], i) == b[i] || queryMax(1, n, 1, i, V[b[i]][right]) == b[i] && queryMin(1, n, 1, i, V[b[i]][right]) == b[i]) {}
else {
cout << "NO" << endl;
return;
}
}
}
cout << "YES" << endl;
}
int main() {
int Case;
cin >> Case;
while (Case--) {
cin >> n;
if (n <= 1e3) {
easy_version();
} else {
hard_version();
}
}
return 0;
}
给你一棵 n n n个点的树, q q q次询问,每次询问会给出一个点 x x x 和 k k k 个要删掉的点,在树上删掉这 k k k 个点和 k k k 个点相连的边后,询问在剩下的若干个连通块中, x x x 能到的最远的点的距离。
首先考虑树的直径的性质:
其次考虑 d f s dfs dfs序的性质: a a a的 d f s dfs dfs序对应 [ i n [ a ] , o u t [ a ] ] [in[a],out[a]] [in[a],out[a]], b b b的 d f s dfs dfs序区间对应 [ i n [ b ] , o u t [ b ] ] [in[b],out[b]] [in[b],out[b]]。
若 $ in[a] 首先算出 d f s dfs dfs序,删掉 k k k个点后,把 d f s dfs dfs序切成若干个区间,区间数是大致是 k − 2 k k-2k k−2k级别的,剩下的区间都是 x x x的可达区间,每个区间对应一个连续的 d f s dfs dfs序当删掉 u u u时,根据 u u u 和 x x x 的关系,有两种情况: l c a ( u , x ) = u lca(u,x)=u lca(u,x)=u,即 u u u是 x x x的祖先,那么由于 u u u不可达了,记 x x x到 u u u的路径上 u u u的直连儿子是 v v v,那么相当于只保留下来 v v v这棵子树内可以到达,也就是 b a n ban ban掉 [ 0 , i n [ v ] ) 、 [ o u t [ v ] , n ) [0,in[v])、[out[v],n) [0,in[v])、[out[v],n)。 u u u和 x x x没有祖先关系,那么由于 u u u不可达,所以 u 的这棵子树不可达了, b a n ban ban掉 [ i n [ u ] , o u t [ u ] ] [in[u],out[u]] [in[u],out[u]] 根据 k k k个点,获取到 k k k个 b a n ban ban掉的区间时,根据上面提到的 d f s dfs dfs序的性质, d f s dfs dfs序只会存在区间嵌套 ( l i < l j < r j < r i ) (l_i< l_j< r_j< r_i) (li<lj<rj<ri)的情况,不会存在两个 d f s dfs dfs区间相交一部分 ( l i < l j < r i < r j ) (l_i< l_j< r_i< r_j) (li<lj<ri<rj)的情况。按左端点增序,左端点相同右端点降序排序遍历,手动去除掉被套在内层的区间,只保留外层的区间。这样得到的若干个区间,就是互不相交的若干个要 b a n ban ban 掉的 d f s dfs dfs 序区间,其补集,就是合法的区间,均与 x x x 连通,利用上文提到的树的直径的性质,统一 m e r g e merge merge 合法区间的直径,用线段树上每一个区间维护这个 d f s dfs dfs 序区间的直径的两个点,求合法区间 [ l , r ] [l,r] [l,r]的直径时,先在线段树上做一个 m e r g e merge merge,再对若干个合法区间做一个 m e r g e merge merge,再和询问点 x x x 做一个 m e r g e merge merge,这样得到了 x x x 连通块的直径的两个端点 x x x 能到的最远点一定是直径两个点中的一个,分别询问距离取 m a x max max 即可。 以下为学习交流QQ群,群号: 546235402,每周题解完成后都会转发到群中,大家可以加群一起交流做题思路,分享做题技巧,欢迎大家的加入。
代码:
#include
学习交流