# C

# 题目大意

你现在有 nn 个用户,mm 个页面,初始时,所有用户都是没有权限访问任何界面的,你现在有三种操作

  • 1 X Y :授予 XX 访问 YY 的权限
  • 2 X :授予 XX 访问所有界面的权限
  • 3 X Y :请输出 XX 是否有访问 YY 的权限

# 数据范围

  • 1N,M2×1051 \le N,M \le 2 \times 10^5
  • 1Q2×1051 \le Q \le 2 \times 10^5

# 题解

# 错解

一开始没想着数据范围,直接用的 vector<vector<bool>> 开了个 NMNM 大小的,显然会 MLEMLE,一个 boolbool 一个字节,NMNM 也就是 4×10104 \times 10^{10} 个字节,40GB40GB 了,直接爆炸了

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int n, m, q, op, x, y;
map<int, vector<bool>> mp;

void solve () {
cin >> n >> m >> q;
vector<bool> flag(n + 1);
vector<vector<bool>> mp(n + 1, vector<bool>(m + 1));

while (q--) {
cin >> op;
if (op == 1)
cin >> x >> y, mp[x][y] = 1;
else if (op == 2)
cin >> x, flag[x] = 1;
else {
cin >> x >> y;

if (flag[x])
cout << "Yes" << endl;
else
cout << (mp[x][y] ? "Yes" : "No") << endl;
}
}
}

# 正解

最多只有 QQ 2e5 个操作,也就是说,刚刚的 4×10104 \times 10^{10}boolbool 里,只有 2×10102 \times 10^{10} 个空间会被用到,我们此时可以考虑用 setset,而不是一次把所有空间都开辟出来

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int n, m, q, op, x, y;
set<int> mp[N];

void solve () {
cin >> n >> m >> q;
vector<bool> flag(n + 1);
while (q--) {
cin >> op;
if (op == 1)
cin >> x >> y, mp[x].insert(y);
else if (op == 2)
cin >> x, flag[x] = 1;
else {
cin >> x >> y;

if (flag[x])
cout << "Yes" << endl;
else
cout << (mp[x].count(y) ? "Yes" : "No") << endl;
}
}
}

# D

# 题目大意

给定一个 A=(a1,...,an)A = (a_1, ..., a_n) 序列和一个非负整数 DD,你现在要做的是,从 AA 中删除尽可能少的元素,得到序列 BB,需要满足:

  • 对于任意 i,j(1i<jB)i, j~(1 \le i < j \le |B|),满足 BiBjD|B_i - B_j| \neq D
    问:需要最少删除的元素个数是?

# 数据范围

  • 1N2×1051 \le N \le 2 \times 10^5
  • 0ai,D1060 \le a_i, D \le 10^6

# 题解

从考虑两个数 BiBjD|B_i - B_j| \neq D 变成,将这个序列变为对于每个数,删到不存在比他大 DD 和小 DD 的数

我们此时,暂且只考虑对于 ii 来说,小 DD 的,不考虑另一半
那么考虑使用 dpdpdp[i][0 / 1] 表示不删除 / 删除ii 的数后,要满足题意的最小操作次数

  • 先行预处理 i=minni=minn+d1i=minn \to i = minn + d - 1,这一段数没有 idi - d 的前驱,故初始化
    • dp[i][1] = cnt[i] ,值为 ii 的删掉就是把所有 ii 都删掉
  • 再考虑 i=minn+di=maxxi = minn + d \to i = maxx
    • 如果不删除 ii
      • dp[i][0] = min(dp[i - d][0] + cnt[i - d], dp[i - d][1])
      • 取 (idi-d 不删的次数 + idi - d 的个数) 和 (idi-d 删掉次数) 两者取 minnminn
    • 如果删除 ii
      • dp[i][1] = min(dp[i - d][0], dp[i - d][1]) + cnt[i]
      • 无论是否删除 idi - dii 都是要删的,故需要加上 cnt[i] ,再加上 (idi-d 删的次数)和(idi-d 不删的次数)取 minnminn 即可

最后再从 maxxmaxx 倒着求到 maxxd+1maxx - d + 1,因为我们之前考虑的是, dp[i][0 / 1] 只考虑了,比 iidd 的不会冲突,所以倒着找也不会出现大 dd 冲突的情况 ``

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <bits/stdc++.h>  
#define int long long
#define endl '\n'

using namespace std;

const int N = 2e5 + 10;
int n, d, a[N]; // 存放原数组
int dp[N * 10][2]; // dp[i][0/1] 表示,选到值 i 时,不删除 / 删除 i 时的最小删除数
int cnt[N * 10];

void solve() {
cin >> n >> d;
int minn = 1e18, maxx = 0;

for (int i = 1; i <= n; i++)
cin >> a[i], minn = min(minn, a[i]), maxx = max(maxx, a[i]), cnt[a[i]]++;

if (d == 0) {
int res = 0;
for (int i = minn; i <= maxx; i++)
// 注意,i 可能没出现过,只要考虑 cnt[i] > 0,即出现过的 i if (cnt[i] > 0)
res += cnt[i] - 1;
cout << res << endl;
return;
}

// 对于 minn -> minn + d - 1,这里面的数,没有 i - d 可以转移
// 故若要删除 i,则必须把所有 i 都删掉,即为 cnt[i] // 不删除 i 的话,那么值默认为 0 就行
for (int i = minn; i < minn + d; i++)
dp[i][1] = cnt[i];
for (int i = minn + d; i <= maxx; i++) {
// 如果不删 i // dp[i - d][0] + cnt[i - d] 要把 i - d 都删了
// dp[i - d][1] 已经把 i - d 删了
dp[i][0] = min(dp[i - d][0] + cnt[i - d], dp[i - d][1]);

// 如果删 i // dp[i - d][0] + cnt[i] i - d 不删,把 i 全删了
// dp[i - d][1] + cnt[i] i - d 和 i 都删了
dp[i][1] = min(dp[i - d][0] + cnt[i], dp[i - d][1] + cnt[i]);
}

int res = 0;
for (int i = maxx; i >= maxx - d + 1; i--)
res += min(dp[i][0], dp[i][1]);

cout << res << endl;
}

signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);

int _ = 1;
// cin >> _;
while (_--)
solve();
}

# E

# 题目大意

你现在有两个空字符串集合 XXYY,有 QQ 个操作,每次操作会给你一个整数 TT 和字符串 SS,你现在需要做的是

  • 如果 TT11,则把 SS 插入集合 XX
  • 如果 TT22,则把 SS 插入集合 YY
    最后问:YY 中有多少个字符串,不以 XX 中的任意一个字符串为前缀

# 数据范围

  • 1Q2×1051 \le Q \le 2 \times 10^5
  • i=1QSi105\sum\limits_{i=1}^{Q} |S_i| \le 10^5

# 题解

这题很明显考察的是字典树

  • clear[i] 记录 ii 位置是否被清除标记
  • val[i] 记录 ii 位置的值
  • res[i] 记录 ii 结点和其子结点的所有满足题意的字符串数量

现在写好了一个 updateupdate 更新操作,其实现是这样的:

  1. 如果 clear[root]11,代表已经被清除,故显然 res[root] = 0
  2. 反之,累加 res[0] -> res[26]val[root]
  • 如果是 11 操作的话,那么标记 clear[结尾最后一个字符]11
  • 如果是 22 操作的话,那么就需要把 val[结尾最后一个字符] 加一

然后执行 updateupdate 操作,由于需要下一层的值,故此处使用递归实现,具体代码如下方所示

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
const int N = 5e5 + 10;

int trie[N][27], clear[N], val[N], res[N], idx = 0;

int q, op, root = 0;
string s;

void update(int root) {
if (clear[root]) {
res[root] = 0;
return;
}

res[root] = val[root];
for (int i = 0; i < 27; i++)
res[root] += res[trie[root][i]];
}

void insert(int op, int pos, int& root) {
if (!root)
root = ++idx;

if (pos == s.size()) {
if (op == 1)
clear[root] = 1;
else
val[root]++;

update(root);
return;
}

insert(op, pos + 1, trie[root][s[pos] - 'a' + 1]);
update(root);
}

void solve () {
cin >> q;
while (q--) {
cin >> op >> s;

insert(op, 0, root);

cout << res[1] << endl;
}
}

# F

# 题目大意

给你一个正整数 NN,请你在所有由 1 + * ( ) 组成的算术表达式中,找出一个长度最小的,使得其值为 NN

# 数据范围

  • 1N20001 \le N \le 2000

# 题解

恐怖如斯无法战胜的 DPDP

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

duoxichangan 微信支付

微信支付

duoxichangan 支付宝

支付宝