2023.7.4 数据结构专题一
T1 Messenger Simulator
luogu link
首先对于第一问非常好做 因为如果收到消息 就是
考虑第二问 我们发现 每次将
点击查看代码
#include <bits/stdc++.h>
#define ls (k << 1)
#define rs (k << 1 | 1)
#define mid (l + r >> 1)
using namespace std;
const int N = 3e5 + 0721;
int lazy[N << 3];
int ans[N], vis[N], newest[N];
int n, m, h, t;
void init() {
t = n + 3e5;
h = 1 + 3e5;
for (int i = 1; i <= n; ++i) newest[i] = i + 3e5, ans[i] = i;
}
void pushdown(int k) {
lazy[ls] += lazy[k];
lazy[rs] += lazy[k];
lazy[k] = 0;
}
void modify(int k, int l, int r, int u, int v) {
// cout << l << " " << r << "\n";
if (l >= u && r <= v) {
++lazy[k];
return;
}
if (lazy[k] != 0) pushdown(k);
if (u <= mid) modify(ls, l, mid, u, v);
if (v > mid) modify(rs, mid + 1, r, u, v);
}
int get_ans(int k, int l, int r, int loc) {
if (l == r) {
return lazy[k];
}
if (lazy[k] != 0) pushdown(k);
if (loc <= mid) return get_ans(ls, l, mid, loc);
else return get_ans(rs, mid + 1, r, loc);
}
int main() {
scanf("%d%d", &n, &m);
init();
for (int i = 1; i <= m; ++i) {
int x;
scanf("%d", &x);
// cout << h << " " << newest[x] - 1<< "\n";
if (h != newest[x]) modify(1, 1, t, h, newest[x] - 1);
if (!vis[x]) ans[x] = max(ans[x], get_ans(1, 1, t, newest[x]) + ans[x]);
else ans[x] = max(ans[x], get_ans(1, 1, t, newest[x]) + 1);
// cout << newest[1] << ' ' << get_ans(1, 1, t, newest[1]) << '\n';
--h;
newest[x] = h;
vis[x] = 1;
}
for (int i = 1; i <= n; ++i) {
if (!vis[i]) ans[i] = max(ans[i], get_ans(1, 1, t, newest[i]) + ans[i]);
else ans[i] = max(ans[i], get_ans(1, 1, t, newest[i]) + 1);
if (vis[i]) printf("1 %d\n",ans[i]);
else printf("%d %d\n", i, ans[i]);
}
return 0;
}
T2 New Year and Conference
luogu link
首先直接枚举这个子集合显然是不可行的 我们考虑缩小规模
发现如果子集合中对于每个冲突 必然是发生在两个集合之间的 所以我们枚举两个集合 让它们在一边冲突一边不冲突即可 显然不会漏情况
但是不能暴力枚举 这是个区间问题 所以我们先考虑按会场一的左端点排序让其一维有序 再解决另一维
我们考虑枚举在会场一不重合的区间 看它们在会场二是否重合
我们用一根扫描线把会场一的区间从左往右扫 假如说我现在的扫描线位置与区间
那么所有右端点被扫描线扫过的区间 都是与
而与
那么我们考虑 扫描线每扫到一个右端点 就把它在会场二所对应的区间都
然后判断
点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define int long long
#define ls (k << 1)
#define rs (k << 1 | 1)
#define mid (l + r >> 1)
#define debug cout << "check\n"
using namespace std;
const int N = 2e5 + 0721;
int a[N << 2], cnt;
int n, m;
struct node {
int sa, sb, ea, eb;
friend bool operator<(node x, node y) {
return x.sa < y.sa;
}
} lec[N];
struct tree {
ll tr[N << 4], lazy[N << 4];
void plus(int k, int l, int r, int v) {
lazy[k] += v;
}
void pushdown(int k) {
lazy[ls] += lazy[k];
lazy[rs] += lazy[k];
lazy[k] = 0;
}
void pushup(int k) {
tr[k] = max(tr[ls] + lazy[ls], tr[rs] + lazy[rs]);
}
void modify(int k, int l, int r, int u, int v) {
if (u <= l && v >= r) {
plus(k, l, r, 1);
return;
}
pushdown(k);
if (u <= mid) modify(ls, l, mid, u, v);
if (v > mid) modify(rs, mid + 1, r, u, v);
pushup(k);
}
ll query(int k, int l, int r, int u, int v) {
if (u <= l && v >= r) {
return tr[k] + lazy[k];
}
pushdown(k);
ll ans = 0;
if (u <= mid) ans = max(ans, query(ls, l, mid, u, v));
if (v > mid) ans = max(ans, query(rs, mid + 1, r, u, v));
pushup(k);
return ans;
}
} seg;
void lsh() {
sort(a + 1, a + 1 + cnt);
m = unique(a + 1, a + 1 + cnt) - a;
for (int i = 1; i <= n; ++i) {
lec[i].sa = lower_bound(a + 1, a + 1 + m, lec[i].sa) - a - 1;
lec[i].sb = lower_bound(a + 1, a + 1 + m, lec[i].sb) - a - 1;
lec[i].ea = lower_bound(a + 1, a + 1 + m, lec[i].ea) - a - 1;
lec[i].eb = lower_bound(a + 1, a + 1 + m, lec[i].eb) - a - 1;
}
}
vector<int> vr[N << 2];
vector<int> vl[N << 2];
bool solve() {
for (int i = 0; i <= m + 2; ++i) {
vr[i].clear();
vl[i].clear();
}
memset(seg.tr, 0, sizeof seg.tr);
memset(seg.lazy, 0, sizeof seg.lazy);
sort(lec + 1, lec + 1 + n);
for (int i = 1; i <= n; ++i) {
int l = lec[i].sa, r = lec[i].ea;
vl[l].push_back(i);
vr[r].push_back(i);
}
for (int i = 0; i <= m + 2; ++i) {
if (vl[i].size()) {
for (int j = 0; j < vl[i].size(); ++j) {
int id = vl[i][j];
if (seg.query(1, 0, m + 2, lec[id].sb, lec[id].eb) != 0) return 1;
}
}
if (vr[i].size()) {
for (int j = 0; j < vr[i].size(); ++j) {
int id = vr[i][j];
seg.modify(1, 0, m + 2, lec[id].sb, lec[id].eb);
}
}
}
return 0;
}
signed main() {
scanf("%lld", &n);
for (int i = 1; i <= n; ++i) {
scanf("%lld%lld%lld%lld", &lec[i].sa, &lec[i].ea, &lec[i].sb, &lec[i].eb);
a[++cnt] = lec[i].sa;
a[++cnt] = lec[i].ea;
a[++cnt] = lec[i].sb;
a[++cnt] = lec[i].eb;
}
lsh();
if (solve()) {
printf("NO");
return 0;
}
for (int i = 1; i <= n; ++i) {
swap(lec[i].sa, lec[i].sb);
swap(lec[i].ea, lec[i].eb);
}
if (solve()) {
printf("NO");
} else
printf("YES");
return 0;
}
T3 Lexicographically Small Enough
因为
因为是字典序 所以按位比较即可
那么一定就是类似于前面一段都相同 然后这一位的
我们考虑怎么换
首先如果这一位已经有
否则我们考虑把
假设它的位置是
然后统计答案
当然也可以让这一位
也是如果本来就有就不用换了
否则查询
注意这样换不仅要统计代价 因为要继续往下推还要考虑对别的字母的影响
显然会让
那么它就等价于让
所以开个线段树或者
点击查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5 + 0721;
const ll inf = 0x7ffffffffffffff;
int n, T;
ll ans, equ;
char s[N], t[N];
struct node {
int tr[N];
inline int lowbit(int x) {
return x & (-x);
}
void update(int x, int val) {
while (x <= n) {
tr[x] += val;
x += lowbit(x);
}
}
ll query(int x) {
ll ret = 0;
while (x) {
ret += tr[x];
x -= lowbit(x);
}
return ret;
}
} bit;
int main() {
cin >> T;
while (T--) {
// cout << "T=" << T << " ";
queue<int> q[26];
scanf("%d", &n);
for (int i = 1; i <= n; ++i) cin >> s[i];
for (int i = 1; i <= n; ++i) cin >> t[i];
// cout << s + 1<< " " << t + 1<< "\n";
ans = inf, equ = 0;
for (int i = 1; i <= n; ++i) bit.tr[i] = 0;
for (int i = 1; i <= n; ++i) {
// cout << s[i] << " " << s[i] - 'a' << "\n";
q[s[i] - 'a'].push(i);
}
for (int i = 1; i <= n; ++i) bit.update(i, 1);
// cout << "check";
for (int i = 1; i <= n; ++i) {
// cout << "i = " << i << " " << ans << " " << T << "\n";
int loc = n + 1;
int ch = t[i] - 'a';
// cout << t[i] << " " << t[i] - 'a' << "\n";
for (int j = 0; j < ch; ++j) {
// cout << j << " " << "check\n";
if (!q[j].empty()) loc = min(loc, q[j].front());
}
if (loc != n + 1) ans = min(ans, equ + bit.query(loc - 1));
if (q[ch].empty()) break;
loc = q[ch].front();
q[ch].pop();
equ += bit.query(loc - 1);
bit.update(loc, -1);
// cout << "i = " << i << " " << ans << " " << T << "\n";
}
printf("%lld\n", (ans == inf ? -1 : ans));
}
return 0;
}
T4 「Wdsr-3」令人感伤的红雨
非常头疼的阅读理解题
因为重点在于记录我不会的东西 然后由于这个
这里给出结论:
并且
重点说一下后续做法
因为每次加操作加的是一个非负数 那么它只会让前面那段区间最大值变得越来越大
所以它就有可能覆盖掉后面一些区间的区间最大值编号
我们注意到整个数列的
所以我们直接把每个能成为任意
这样每次区间加就查询当前区间内最靠后的一个代表点 然后比较它和下一个代表点的差值
如果加的这个值大于这个差值 就把下一个代表点吞掉 父亲连上 链表修改 继续往下查
然后查询的时候路径压缩即可
点击查看代码
#include <bits/stdc++.h>
using namespace std;
const int N = 6e6 + 0721;
int fa[N], dis[N], nxt[N];
int a[N];
int n, q, maxn, maxid;
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
int main() {
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
if (a[i] >= maxn) {
nxt[i] = n + 1;
dis[maxid] = a[i] - maxn;
nxt[maxid] = i;
maxn = a[i];
maxid = i;
}
fa[i] = maxid;
}
while (q--) {
int opt, l, r;
scanf("%d%d%d", &opt, &l, &r);
if (opt == 1) {
int fl = find(l);
int tmp;
dis[fl] -= r;
while (dis[fl] < 0 && nxt[fl] <= n) {
tmp = nxt[fl];
nxt[fl] = nxt[tmp];
dis[fl] += dis[tmp];
fa[tmp] = fl;
dis[tmp] = -1;
nxt[tmp] = n + 1;
}
}
else {
int fr = find(r);
if (fr >= l) printf("0\n");
else printf("%d\n",l - fr);
}
}
return 0;
}
T5 [USACO20OPEN] Favorite Colors G
首先我们不难发现这样一个事情:由同一个点出发到达的点必然都是同一个颜色
那我们如果考虑将它们合并 那么由这些同色的点出发到达的所有点必然也都是同一个颜色
所以我们考虑这样一直合并下去 直到所有点的出度
合并的时候考虑启发式合并 就可以做到
或者可以链表缩小复杂度(?
- 启发式合并 合完之后记得删掉原来的边 记得合的是
和
点击查看代码
#include <bits/stdc++.h>
using namespace std;
namespace steven24 {
const int N = 2e5 + 0721;
vector<int> e[N];
vector<int> son[N];
int fa[N];
int col[N], cnt;
int n, m;
queue<int> q;
inline int read() {
int xr = 0, F = 1;
char cr;
while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
while (cr >= '0' && cr <= '9')
xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
return xr * F;
}
void write(int x) {
if (x < 0) putchar('-'), x = -x;
if (x >= 10) write(x / 10);
putchar(x % 10 + '0');
}
void merge(int x, int y) {
x = fa[x], y = fa[y];
if (son[x].size() < son[y].size()) swap(x, y);
for (int v : son[y]) {
fa[v] = x;
son[x].emplace_back(v);
}
for (int v : e[y]) {
e[x].emplace_back(v);
}
if (e[x].size() > 1) q.push(x);
}
void main() {
n = read(), m = read();
for (int i = 1; i <= m; ++i) {
int x = read(), y = read();
e[x].emplace_back(y);
}
for (int i = 1; i <= n; ++i) {
son[i].emplace_back(i);
fa[i] = i;
if (e[i].size() > 1) q.push(i);
}
while (!q.empty()) {
int now = q.front();
q.pop();
int s = e[now][0];
for (int i = 1; i < (int)e[now].size(); ++i) {
int y = e[now][i];
if (fa[y] == fa[s]) continue;
merge(s, y);
}
while (e[now].size() > 1) e[now].pop_back();
}
for (int i = 1; i <= n; ++i) {
if (!col[fa[i]]) col[fa[i]] = ++cnt;
write(col[fa[i]]), putchar('\n');
}
}
}
int main() {
// freopen("P6279_2.in", "r", stdin);
// freopen("P6279.out", "w", stdout);
steven24::main();
return 0;
}
/*
9 12
1 2
4 2
5 8
4 6
6 9
2 9
8 7
8 3
7 1
9 4
3 5
3 4
*/
T6 Guessing Permutation for as Long as Possible
要求在所有的询问之前都无法得到答案
考虑一个三元组
我们分情况讨论:
- 当
为最后一次询问 那么如果 与 的询问得到的大小关系相同 那么直接就能推出 所以此时前两个询问得到的大小关系一定不同 - 当
为最后一次询问 那么如果 与 的询问得到的大小关系不同 那么直接就能推出 所以此时前两个询问得到的大小关系一定相同 - 当
为最后一次询问 那么如果 与 的询问得到的大小关系不同 那么直接就能推出 所以此时前两个询问得到的大小关系一定相同
那么此时我们就得到了类似于 “某两个元素之间的大小关系必须相同 某两个元素之间的大小关系不同” 的信息
我们直接使用扩展域并查集 维护对于每个询问来说 该次询问对应的两个元素之间的大小关系
那么就会得到很多很多连通块 相同块里的询问对应的两个元素之间的大小关系必须相同
连通块与连通块之间的取值是没有影响的 所以最终答案就是
点击查看代码
#include <bits/stdc++.h>
using namespace std;
namespace steven24 {
const int N = 521;
const int M = 5e5 + 0721;
const int mod = 1e9 + 7;
int q[N][N];
int fa[M];
bool vis[M];
bool cal[M];
int n, m;
inline int read() {
int xr = 0, F = 1;
char cr;
while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
while (cr >= '0' && cr <= '9')
xr = (xr << 3) + (xr << 1) + (cr ^ 48), cr = getchar();
return xr * F;
}
void init() {
for (int i = 1; i <= (m << 1); ++i) fa[i] = i;
}
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
int ksm(int x, int y) {
int ret = 1;
while (y) {
if (y & 1) ret = 1ll * ret * x % mod;
x = 1ll * x * x % mod;
y >>= 1;
}
return ret;
}
void main() {
n = read();
m = (n * (n - 1) >> 1);
for (int i = 1; i <= m; ++i) {
int x = read(), y = read();
q[x][y] = q[y][x] = i;
}
init();
for (int i = 1; i <= n; ++i) {
for (int j = i + 1; j <= n; ++j) {
for (int k = j + 1; k <= n; ++k) {
int x = q[i][j], y = q[j][k], z = q[i][k];
if (z > x && z > y) { //(i, k)最后问 说明(i, j)和(j, k)的两个询问大小关系不同
int fx = find(x), fy = find(y), fmx = find(x + m), fmy = find(y + m);
if (fx == fy) {
printf("0\n");
return;
}
if (fy == fmx) continue;
fa[fx] = fmy;
fa[fmx] = fy;
} else if (x > y && x > z) { //(i, j)最后问 说明(i, k)和(j, k)的两个询问大小关系相同
int fy = find(y), fz = find(z), fmy = find(y + m), fmz = find(z + m);
if (fy == fmz) {
printf("0\n");
return;
}
if (fy == fz) continue;
fa[fy] = fz;
fa[fmy] = fmz;
} else { //(j, k)最后问 说明(i, j)和(i, k)的两个询问大小关系相同
int fx = find(x), fz = find(z), fmx = find(x + m), fmz = find(z + m);
if (fx == fmz) {
printf("0\n");
return;
}
if (fx == fz) continue;
fa[fx] = fz;
fa[fmx] = fmz;
}
}
}
}
int cnt = 0;
for (int i = 1; i <= m; ++i) if (fa[i] == i) ++cnt;
printf("%d\n", ksm(2, cnt));
}
}
int main() {
steven24::main();
return 0;
}
/*
4
2 1
4 1
3 1
2 4
4 3
2 3
*/
T7 生日礼物
首先我们考虑简化问题 首先把连续的正数和连续的负数全连在一起 这肯定是没有问题的
那么现在就得到了一个正负相间的区间
如果正区间的数量
我们现在考虑
考虑选或者不选每个数对于答案的贡献
我们发现如果选上一个负数 我们就可以减少一个正区间 带来的代价就是这个负数的大小
如果不选一个正数 带来的代价就是它的相反数 也可以减少一个正区间
所以我们按绝对值大小排序 从小到大选择合并 直到正区间的数量为
T8 [NOIP2016 提高组] 蚯蚓
首先要考虑生长标记的处理 我们肯定不能给其余每个都暴力加上
但是我们发现 不加
本着“你亏了就是我赚了”的原则 我们可以单独给这一条减去
然后给整体加上
这样查询最大值的时候直接加上这个懒标记即可
查询的时候因为每次拿出来一个 送回去两个 想到优先队列
但是这题的数据范围需要一个线性做法
首先我们考虑这样一件事 假如我切开了最长那条蚯蚓 剩下了
然后我下一次再找最长的 因为凡是被切开的 有且仅一次少了
所以无论怎么切 都会有这次切的蚯蚓的原长小于等于上一次的
那么切下来的两半显然满足单调性
所以我们开三个队列维护 每次取出队首的三个元素比较即可
点击查看代码
/*
首先输入所有蚯蚓 然后排序
然后每次切 取三个队头比较 并且记录最大值出现在哪个队列里
将该蛇取出 加上懒标记并剪断
懒标记 += q
剪断的两条蛇分别减去懒标记
*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
int xr = 0, F = 1;
char cr;
while (cr = getchar(), cr < '0' || cr > '9') if (cr == '-') F = -1;
while (cr >= '0' && cr <= '9')
xr = (xr << 1) + (xr << 3) + (cr ^ 48), cr = getchar();
return xr * F;
}
void write(ll x) {
int wtop = 0;
char ws[51];
if (x < 0) x = -x, putchar('-');
do {
ws[++wtop] = x % 10 + '0';
x /= 10;
} while (x);
for (int i = wtop; i; --i) putchar(ws[i]);
}
namespace steven24 {
const int N = 1e5 + 0721;
const ll inf = 0x7fffffffffffffff;
int a[N];
int n, m, q, u, v, t;
ll lazy;
double p;
deque<ll> dq[4];
vector<ll> ans;
inline bool cmp(ll x, ll y) {
return x > y;
}
void main() {
n = read(), m = read(), q = read(), u = read(), v = read(), t = read();
p = 1.0 * u / v;
for (int i = 1; i <= n; ++i) a[i] = read();
sort(a + 1, a + 1 + n, cmp);
for (int i = 1; i <= n; ++i) dq[1].push_back(a[i]);
for (int i = 1; i <= m; ++i) {
ll maxn = -inf;
int maxid;
for (int j = 1; j <= 3; ++j) {
if (!dq[j].empty() && dq[j].front() > maxn) {
maxn = dq[j].front();
maxid = j;
}
}
dq[maxid].pop_front();
maxn += lazy;
int x1 = floor(maxn * p);
int x2 = maxn - x1;
lazy += q;
x1 -= lazy, x2 -= lazy;
dq[2].push_back(x1);
dq[3].push_back(x2);
if (i % t == 0) write(maxn), putchar(' ');
}
putchar(' '), putchar('\n');
for (int i = 1; i <= 3; ++i) {
for (int j = 0; j < dq[i].size(); ++j) ans.push_back(dq[i][j] + lazy);
}
sort(ans.begin(), ans.end(), cmp);
for (int j = t; j <= ans.size(); j += t) write(ans[j - 1]), putchar(' ');
putchar(' '), putchar('\n');
}
}
int main() {
steven24::main();
return 0;
}
T9 [CSP-S2020] 贪吃蛇
考虑递归思考的这么一件事
首先如果当前最长的蛇吃了最短的蛇 没有变成最短的蛇
那么如果第二长的蛇吃 必是吃了第二短的蛇 那么一定会剩下更短
那么第二长的蛇一定会想尽办法保证不死 那第一条蛇就可以放心地吃
如果吃完变成了最短的蛇 我们考虑下一条蛇的选择
如果它吃了 它也变成了最短的蛇 那么就需要考虑第三条的选择
假如第三条发现 它吃了不会变成最短的 那么它就会放心吃
那么第二条一定不会去吃
那么第一条就可以放心地吃
发现这就是一个奇偶性的问题了
所以首先 我们一直让它吃直到它吃完会变成最短
然后我们往下一点点考虑 直到发现有一条蛇吃完不会变得最短
然后就可以根据奇偶性来判断这条蛇的选择了
因为我们要每次拿出最长的和最短的 很容易想到用二叉堆
但是还是要线性做法
然后我们还是发现吃完剩一段的蛇还是具有单调性的
所以还是可以开两个队列来维护
T10 Omkar and Time Travel
一切都是命运石之门的选择!
选择完了开始头疼 非常的头疼
区间问题考虑起手排序 这题显然右端点先到的先被传送 所以按右端点排好一点
设
因为传送回去了所有左端点大于
所以
不难发现这是一个经典的二维偏序
然后我们考虑怎么统计答案
不难发现
发现对于集合
那么第一次的显然要依次经过右端点小于它的右端点的所有传送门并且被强制传送回去
那就是右端点小于它的
然后我们回到了
那么传送次数就是右端点小于它并且左端点大于
还是个二维偏序
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)