版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_39972971/article/details/82595840
【比赛链接】
【题解链接】
**【A】**Timetable
【思路要点】
- 首先,若存在任何一组合法解,有 。
- 对于每一个 ,应当满足 。
- 用差分+前缀和处理出所有需要满足 的位置,并且在综合 和 的限制下构造出使得每一个 尽可能小的解,检验是否满足 。
- 若满足,方案即为构造出的 数组,否则问题无解。
- 时间复杂度 。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int n, x[MAXN], cnt[MAXN][2];
ll t, a[MAXN], b[MAXN];
int main() {
read(n), read(t);
for (int i = 1; i <= n; i++)
read(a[i]);
for (int i = 1; i <= n; i++) {
read(x[i]);
if (x[i] < i) {
printf("No\n");
return 0;
}
chkmax(b[x[i]], a[i] + t);
cnt[1][0]++, cnt[i][0]--;
cnt[x[i] + 1][0]++, cnt[n + 1][0]--;
cnt[i][1]++, cnt[x[i]][1]--;
}
for (int i = 1; i <= n; i++) {
cnt[i][0] += cnt[i - 1][0];
cnt[i][1] += cnt[i - 1][1];
if (cnt[i][0]) chkmax(b[i], a[i] + t);
if (cnt[i][1]) chkmax(b[i], a[i + 1] + t);
chkmax(b[i], b[i - 1] + 1);
}
for (int i = 1; i <= n; i++) {
if (x[i] == n) continue;
if (b[x[i]] >= a[x[i] + 1] + t) {
printf("No\n");
return 0;
}
}
printf("Yes\n");
for (int i = 1; i <= n; i++)
write(b[i]), putchar(' ');
printf("\n");
return 0;
}
**【B】**Subway Pursuit
【思路要点】
- 我们可以通过朴素的二分在 的操作次数内将目标确定在一个长度为 的区间内。
- 注意到操作次数很多,我们不妨随机选取该区间内的一个位置询问,若找到目标,则问题解决,否则,下一秒目标可能出现的位置扩大为一个长度为 的区间,我们仍然可以通过朴素的二分将其确定在一个长度为 的区间内并重复随机猜测。
- 由一个长度为 的区间通过二分确定一个长度为 的区间之多需要使用 次操作,因此我们每 次操作就能进行一次正确率不低于 猜测,在次数限制内能够进行的操作数约为 ,每一次都未猜对的概率约为 。
- 时间复杂度 ,在规定的操作次数内完成任务的概率约为 ,应确保使用的随机方式
没有被卡足够随机。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
ll n; int k;
unsigned seed;
unsigned Rand() {
seed = seed * seed + rand();
return seed;
}
int main() {
read(n), read(k);
ll l = 1, r = n;
while (true) {
if (r - l + 1 <= 4 * k + 5) {
int len = r - l + 1;
int tmp = Rand() % len;
cout << l + tmp << ' ' << l + tmp << endl;
string res; cin >> res;
if (res == "Yes") return 0;
l = max(l - k, 1ll);
r = min(r + k, n);
} else {
ll mid = (l + r) / 2;
cout << l << ' ' << mid << endl;
string res; cin >> res;
if (res == "Yes") {
l = max(l - k, 1ll);
r = min(mid + k, n);
} else {
l = max(mid + 1 - k, 1ll);
r = min(r + k, n);
}
}
}
return 0;
}
**【C】**Network Safety
【思路要点】
- 对于一条边,它会造成故障当且仅当它一侧的节点被入侵,另一侧的没有,并且入侵的病毒编号为其两侧的点权异或和。
- 因此,对于一种病毒 ,两侧的点权异或和为 的边连通的每一个连通分量要么都被入侵,要么都不被入侵,即记连通分量数为 , 对答案的贡献为 。
- 用并查集处理存在两侧的点权异或和为 的边的 ,其余的 贡献均为 。
- 时间复杂度 。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e5 + 5;
const int P = 1e9 + 7;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
struct info {int l, r; ll delta; } a[MAXN];
int n, m, k, bit[MAXN], f[MAXN];
bool vis[MAXN]; ll x[MAXN];
bool cmp(info a, info b) {
return a.delta < b.delta;
}
int F(int x) {
if (f[x] == x) return x;
else return f[x] = F(f[x]);
}
int main() {
read(n), read(m), read(k);
for (int i = 1; i <= n; i++)
read(x[i]);
bit[0] = 1;
for (int i = 1; i <= n; i++)
bit[i] = bit[i - 1] * 2 % P;
for (int i = 1; i <= m; i++) {
read(a[i].l), read(a[i].r);
a[i].delta = x[a[i].l] ^ x[a[i].r];
}
sort(a + 1, a + m + 1, cmp);
int cnt = 0, ans = 0;
for (int i = 1, nxt; i <= m; i = nxt + 1) {
for (nxt = i; nxt < m && a[nxt + 1].delta == a[i].delta; nxt++);
int comp = n; cnt++;
for (int j = i; j <= nxt; j++) {
f[a[j].l] = a[j].l;
f[a[j].r] = a[j].r;
comp -= !vis[a[j].l];
vis[a[j].l] = true;
comp -= !vis[a[j].r];
vis[a[j].r] = true;
}
for (int j = i; j <= nxt; j++)
f[F(a[j].l)] = F(a[j].r);
for (int j = i; j <= nxt; j++) {
if (vis[a[j].l]) comp += F(a[j].l) == a[j].l;
vis[a[j].l] = false;
if (vis[a[j].r]) comp += F(a[j].r) == a[j].r;
vis[a[j].r] = false;
}
ans = (ans + bit[comp]) % P;
}
ans = (ans + ((1ll << k) - cnt) % P * bit[n]) % P;
writeln(ans);
return 0;
}
**【D】**You Are Given a Tree
【思路要点】
- 有一种朴素的贪心,可以对于一个任意的 ,在 的时间内求出答案,即从叶子向根贪心,若能够在某个点的子树内形成一条路径,则令其形成,否则返回子树内能够延伸的最长长度。
- 我们发现对于一个任意的 , 并且 ,因此若我们设置一个阈值 ,当 时直接用上述贪心计算答案,否则利用 的单调性二分出每一种 取值的 的范围,时间复杂度将为 。
- 令 ,时间复杂度 。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
vector <int> a[MAXN];
int n, tot, father[MAXN], p[MAXN], ans[MAXN];
void work(int pos, int fa) {
father[pos] = fa;
for (unsigned i = 0; i < a[pos].size(); i++)
if (a[pos][i] != fa) work(a[pos][i], pos);
p[++tot] = pos;
}
int f(int x) {
if (ans[x] != -1) return ans[x];
static int res[MAXN]; int tans = 0;
for (int i = 1; i <= n; i++) {
int Max = 1, Nax = 1, pos = p[i];
for (unsigned i = 0; i < a[pos].size(); i++)
if (a[pos][i] != father[pos]) {
int tmp = res[a[pos][i]] + 1;
if (tmp > Max) {
Nax = Max;
Max = tmp;
} else chkmax(Nax, tmp);
}
if (Max + Nax - 1 >= x) tans++, res[pos] = 0;
else res[pos] = Max;
}
return ans[x] = tans;
}
int main() {
read(n);
for (int i = 1; i <= n - 1; i++) {
int x, y; read(x), read(y);
a[x].push_back(y);
a[y].push_back(x);
}
work(1, 0);
memset(ans, -1, sizeof(ans));
for (int i = 1, nxt; i <= n; i = nxt + 1) {
int now = f(i);
if (now >= 65 || f(i + 1) != now) {
ans[nxt = i] = now;
continue;
}
int l = i + 1, r = n;
while (l < r) {
int mid = (l + r + 1) / 2;
if (f(mid) == now) l = mid;
else r = mid - 1;
}
nxt = l;
for (int j = i; j <= nxt; j++)
ans[j] = now;
}
for (int i = 1; i <= n; i++)
writeln(ans[i]);
return 0;
}
**【E】**Summer Oenothera Exhibition
【思路要点】
- 原题本质是对于每个询问 ,将序列分为最少的若干段,使得每一段的极差不超过 ,求最少分的段数 。
- 显然一个朴素的“尽量分在前面一段”贪心可以对于每个询问,在 的时间内给出答案,并且另一个简单的事实是我们可以通过 表在 的时间内确定一段的结尾位置。
- 考虑进行平衡规划,我们设定一个阈值 ,维护每一个点 向后最长能够保证满足极差限制的位置 (仅维护到 ),以及每一个点 通过 在不走过 的情况下向后最长走的段数 和走到的位置 。以上信息能够在 的时间内进行维护。
- 对于 的 ,我们用 向后模拟,次数不会超过 ;对于 的 ,我们通过 表在 的时间内确定这一段的结尾位置,次数同样不会超过 ,至此,我们得到了一个时间复杂度为 的做法,我们发现二分部分的时间复杂度过高,导致总体时间复杂度无法通过设置 显著降低。
- 再次进行平衡规划,设定一个阈值 ,对 的维护由仅维护到 改为维护到 ,则维护信息的时间复杂度变为 。
- 对于 的 ,我们用 向后模拟,次数不会超过 ;对于 的 ,我们用 向后模拟,次数不会超过 ;对于 的 ,我们通过 表在 的时间内确定这一段的结尾位置,次数不会超过 。至此,我们得到了一个时间复杂度为 的做法。
- 令 ,时间复杂度 。
【代码】
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int MAXLOG = 20;
const int INF = 1e9 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
struct info {int x, home; } b[MAXN];
bool operator < (info a, info b) {return a.x > b.x; }
int n, w, q, k1, k2, a[MAXN];
int cnt[MAXN], jump[MAXN], ans[MAXN];
int nxt[MAXN], nowMax[MAXN], nowMin[MAXN];
int Min[MAXN][MAXLOG], Max[MAXN][MAXLOG];
priority_queue <info> Heap;
bool cmp(info a, info b) {
return a.x < b.x;
}
void update(int pos, int x) {
while (nxt[pos] <= n && nxt[pos] - pos <= k2 && nowMax[pos] - nowMin[pos] <= x) {
nxt[pos]++;
chkmax(nowMax[pos], a[nxt[pos]]);
chkmin(nowMin[pos], a[nxt[pos]]);
}
if (nxt[pos] <= n && nxt[pos] - pos <= k1) Heap.push((info) {nowMax[pos] - nowMin[pos], pos});
static int q[MAXN]; int top = 0;
jump[pos] = pos, cnt[pos] = 0, q[++top] = pos;
while (jump[pos] != n + 1 && nxt[jump[pos]] - pos <= k1) {
jump[pos] = nxt[jump[pos]];
cnt[pos]++, q[++top] = jump[pos];
}
static int tmp[MAXN]; tmp[pos] = top;
static bool updated[MAXN]; updated[pos] = true;
for (int i = pos - 1; i >= 1 && i >= pos - k1; i--) {
if (!updated[nxt[i]]) continue;
while (q[top] - i > k1) top--;
jump[i] = q[top], cnt[i] = cnt[nxt[i]] + 1 - tmp[nxt[i]] + top;
tmp[i] = top, updated[i] = true;
}
for (int i = pos; i >= 1 && i >= pos - k1; i--) updated[i] = false;
}
int query(int x) {
static int tmp[MAXN]; int tot = 0;
while (!Heap.empty() && Heap.top().x <= x) {
tmp[++tot] = Heap.top().home;
Heap.pop();
}
sort(tmp + 1, tmp + tot + 1);
for (int i = 1; i <= tot; i++)
update(tmp[i], x);
int ans = 0, pos = 1;
while (pos != n + 1) {
int dist = nxt[pos] - pos;
if (dist <= k1) {
ans += cnt[pos];
pos = jump[pos];
} else {
while (nxt[pos] <= n && nxt[pos] - pos <= k2 && nowMax[pos] - nowMin[pos] <= x) {
nxt[pos]++;
chkmax(nowMax[pos], a[nxt[pos]]);
chkmin(nowMin[pos], a[nxt[pos]]);
}
dist = nxt[pos] - pos;
if (dist <= k2) {
ans += 1;
pos = nxt[pos];
} else {
ans += 1;
int nMax = a[pos], nMin = a[pos];
for (int i = MAXLOG - 1; i >= 0; i--) {
if (pos + (1 << i) - 1 <= n) {
int tMax = max(nMax, Max[pos][i]);
int tMin = min(nMin, Min[pos][i]);
if (tMax - tMin <= x) {
pos += 1 << i;
nMax = tMax;
nMin = tMin;
}
}
}
}
}
}
return ans;
}
void init() {
a[n + 1] = -INF;
for (int p = 1; p < MAXLOG; p++)
for (int i = 1, j = (1 << (p - 1)) + 1; j <= n; i++, j++) {
Max[i][p] = max(Max[i][p - 1], Max[j][p - 1]);
Min[i][p] = min(Min[i][p - 1], Min[j][p - 1]);
}
k1 = pow(n, 1.0 / 3.0), k2 = pow(n, 2.0 / 3.0);
for (int i = 1; i <= n; i++) {
nxt[i] = i + 1;
nowMin[i] = min(a[i], a[i + 1]);
nowMax[i] = max(a[i], a[i + 1]);
Heap.push((info) {nowMax[i] - nowMin[i], i});
jump[i] = min(i + k1, n + 1);
cnt[i] = jump[i] - i;
}
}
int main() {
read(n), read(w), read(q);
for (int i = 1; i <= n; i++) {
read(a[i]);
Max[i][0] = Min[i][0] = a[i];
}
init();
for (int i = 1; i <= q; i++) {
int x; read(x);
b[i] = (info) {w - x, i};
}
sort(b + 1, b + q + 1, cmp);
for (int i = 1; i <= q; i++)
ans[b[i].home] = query(b[i].x);
for (int i = 1; i <= q; i++)
writeln(ans[i] - 1);
return 0;
}