赞
踩
给出 n n n个限制条件,问有多少个数字 k k k同时满足这些限制条件。
限制条件分为以下三种:
k k k必须大于等于给出的一些数字 x x x
k k k必须小于等于给出的一些数字 x x x
k k k不能与给出的数字 x x x相同
对于限制条件1, 2,可以使用两个变量维护数字 k k k的取值区间。
对于限制条件3,可以采用容器(set, map等)存储这些不能取到的数字。
根据限制条件1,2得到的区间,再遍历容器检查区间内有多少数字不能被取到,剩下的数字个数就是答案。
#include<bits/stdc++.h> using namespace std; typedef long long ll; set<int> s; void solve() { int n; cin >> n; int l = -1e9, r = 1e9; s.clear(); for (int i = 0; i < n; i++) { int a, x; cin >> a >> x; if (a == 1) { l = max(l, x); } else if (a == 2) { r = min(r, x); } else { s.insert(x); } } int ans = r - l + 1; for (auto i : s) { if (i >= l && i <= r) ans--; } cout << max(0, ans) << endl; } int main() { int Case; cin >> Case; while (Case--) { solve(); } return 0; }
有一个包含
n
n
n个数字的序列
A
=
a
1
,
a
2
,
.
.
.
,
a
n
A = a_1, a_2, ..., a_n
A=a1,a2,...,an,Alice和Bob将会执行以下操作:
首先,Alice将会移除序列中至多
k
k
k个数字。
然后,Bob将会选择剩余的序列中至多
x
x
x个数字并让这些数字乘上
−
1
-1
−1。
Alice想让剩下的数字之和尽可能大,但Bob想让剩下的数字之和尽可能小,假设两人均采取最优策略,那么剩下的数字之和会是多少?
实际上,Bob会做的事情是固定的,即选择剩余的数组中最大的
x
x
x个数字进行操作。
既然Bob的操作固定了,那就可以思考Alice的操作了,由于Alice想要结果尽可能大,那么Alice的操作就是删除部分最大的数字,由于不知道删除多少个是最优的,因此需要对删除的数字个数进行枚举,记录最大的结果。
对删除后剩余的数字总和进行计算可以先对数组进行排序,并维护前缀和,使用前缀和来计算结果。
hint: 删除元素后剩余的数字不足 x x x个时,计算区间和时需避免出现下标越界。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 2e5 + 5e2; int a[N], pre[N]; void solve() { int n, k, x; cin >> n >> k >> x; for (int i = 1; i <= n; i++) { cin >> a[i]; } sort(a + 1, a + n + 1); for (int i = 1; i <= n; i++) { pre[i] = pre[i - 1] + a[i]; } int ans = -1e9; for (int i = 0; i <= k; i++) { int m = n - i; int sum; /*小心下标越界*/ if (m > x) sum = pre[m - x] - (pre[m] - pre[m - x]); else sum = -pre[m]; ans = max(ans, sum); } cout << ans << endl; } int main() { int Case; cin >> Case; while (Case--) { solve(); } return 0; }
给出一个包含 n n n个数字的数组 A = a 1 , a 2 , . . . , a n A = a_1, a_2, ..., a_n A=a1,a2,...,an,你可以选择一个数字 k k k,并对数字进行以下操作:
将数组均分为 n k \frac{n}{k} kn个子数组,子数组形如: [ a 1 , a 2 , . . . , a k ] , [ a k + 1 , a k + 2 , . . . , a 2 k ] , . . . , [ a n − k + 1 , a n − k + 2 , . . . , a n ] [a_1, a_2, ..., a_k], [a_{k + 1}, a_{k + 2}, ..., a_{2k}], ..., [a_{n - k + 1}, a_{n - k + 2}, ..., a_n] [a1,a2,...,ak],[ak+1,ak+2,...,a2k],...,[an−k+1,an−k+2,...,an]
如果可以选择一个数字 m m m,让数组中所有元素对 m m m取余数,且计算后得到的各个子数组均是相同的,那么就可以获得一点积分。
问:最多可以获得多少积分(一个数字 k k k最多产生一点积分)。
首先思考 k k k的选择,不难发现只有 k k k为 n n n的因子时各个子数组的长度才是相等的,因此只需要考虑 n n n的所有因子作为 k k k的选择。
当只有两个数字 x , y x, y x,y时,那么只要这两个数之间差的绝对值取模 m m m的结果为0,那么这两个数字对于 m m m取模的结果必然是相同的。
将结论推广至全部数组中的内容,为了尽可能使得到的 m m m更大,令 m m m即为所有子数组中对位元素的差的绝对值之间的最大公约数。
为了便于计算,每次计算只考虑相邻子数组中的元素进行对位计算,即计算:
m = g c d ( ∣ a 1 − a 1 + k ∣ , ∣ a 2 − a 2 + k ∣ , . . . , ∣ a n − k + 1 − a n ∣ ) m = gcd(|a_1 - a_{1 + k}|, |a_2 - a_{2 + k}|, ..., |a_{n - k + 1} - a_n|) m=gcd(∣a1−a1+k∣,∣a2−a2+k∣,...,∣an−k+1−an∣)
由于题目要求的 m ≥ 2 m \ge 2 m≥2,那么只要得到的结果不为1,那么就表示当前存在合法的 m m m能获得积分。
Tips: 当数组中对位元素之差均为0时,即各子数组中相同位置的元素均相等,此时得到的最大公约数为 0 0 0,但此时任选一个 m m m均为合法的解,因此也可以获得积分。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 2e5 + 5e2; int a[N]; void solve() { int n; cin >> n; for (int i = 1; i <= n; i++) { cin >> a[i]; } int ans = 0; for (int k = 1; k <= n; k++) { if (n % k == 0) { int m = 0; for (int j = 1; j + k <= n; j++) { m = __gcd(m, abs(a[j + k] - a[j])); } if (m != 1) ans++;//m>=2 || m == 0 } } cout << ans << endl; } int main() { int Case; cin >> Case; while (Case--) { solve(); } return 0; }
开始时有一个空的数组 a a a,将对这个数组进行 n n n次操作,每次操作为以下两种操作之一:
选择一个数字 x x x,将这个数字添加到数组 a a a的末尾。
将数组 a a a的内容复制 x x x遍放在数组 a a a的后面。
结束操作后,会给出 q q q个询问,每个询问需要你回答数组中第 k k k个元素是多少。
虽然操作 2 2 2会使数组中数字总数增加的非常快,但是由于题目的询问最多只会到 1 0 18 10^{18} 1018,因此,当数组中总数超过 1 0 18 10^{18} 1018时,就不需要再进行操作了。
可以使用结构体记录下每次操作后数组中的数字总数 s u m sum sum以及操作 1 1 1添加进来的数字个数 c n t cnt cnt。如果是操作 2 2 2,还需要记录下操作 2 2 2的复制次数 m u l mul mul。
对于操作 1 1 1,还需将添加的数字记录到另一个数组 a a a中。
然后对于每次查询,均可通过递归的方式从记录的操作信息回推到某一条操作 1 1 1对应的数字:
如果当前查询的第 k k k个元素与当前结构体记录的总数字个数 s u m sum sum相等,那么此时的答案就是数组 a a a中第 c n t cnt cnt个数字。
如果不相等,说明需将本轮的复制操作消除,继续递归查找,将复制消除后,需将 k k k也修改为 k m o d s u m m u l k \text{ } mod \text{ } \frac{sum}{mul} k mod mulsum,而且需要注意,如果余数为 0 0 0,需要将 k k k修改为 s u m m u l \frac{sum}{mul} mulsum,计算得到 k k k后继续递归查询。
对于每次递归查询,遇到操作 1 1 1就会返回结果,遇到操作 2 2 2就会移除复制操作后递归查询,查询时间复杂度为对数级别。
坑点:
虽然我们手动设置数字上限为
1
0
18
10^{18}
1018,但如果使用long long类型存储,依然不能保证运算中不会发生溢出,因此需要使用__int128来进行存储。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N = 2e5 + 5e2; struct Node{ __int128 sum;//数字总数 int cnt, mul;//操作1的次数,mul为如果当前操作为操作2,那么记录复制的次数 bool operator < (const Node &o) const { if (sum != o.sum) return sum < o.sum; return cnt < o.cnt; } }; int n, q; vector<int> a;//数字表 vector<Node> num;//信息表 int search(ll k) {查询第k个数字 //在信息表里查 int id = lower_bound(num.begin(), num.end(), Node{k, 0}) - num.begin(); //当前查询的k与信息表的数字总数相等,那么此时就是对应信息表中记录的最后一次操作1插入的数字 if (num[id].sum == k) { return a[num[id].cnt - 1]; } k %= num[id].sum / num[id].mul;//否则计算在当前的操作2前第k个数字对应的数字是第几个 if (k == 0) k = num[id].sum / num[id].mul; return search(k);//递归查询 } void solve() { a.clear(); num.clear(); cin >> n >> q; __int128 sum = 0; int cnt = 0; for (int i = 0; i < n; i++) { ll b, x; cin >> b >> x; int mul = 0; if (sum <= 1e18) {//只有数字总数小于等于10的18次方才记录操作信息 if (b == 1) { cnt++; sum++; a.push_back(x); } else { mul = x + 1; sum *= mul;//如果是long long类型,这里就可以发生溢出 } num.push_back(Node{sum, cnt, mul}); } } while (q--) { ll k; cin >> k; cout << search(k) << ' '; } cout << endl; } int main() { int Case; cin >> Case; while (Case--) { solve(); } return 0; }
对于一个只含有 0 0 0和 1 1 1的字符串 s s s,如果它的子串 s ^ \hat{s} s^只含有 1 1 1个 1 1 1,我们就称他为好子串。
现在我们希望知道,有多少个字符串 s s s满足以下要求:
答案可能很大,需要对
998244353
998244353
998244353取模。另外,在考虑子串时,我们需要考虑位置。比如说,1010中,我们认为有
2
2
2个子串
10
10
10,因为他们的位置是不同的。
对于由若干个
1
1
1和
0
0
0组成的字符串
s
s
s,我们可以认为:每一个
1
1
1都可以和它之前、之后的
0
0
0进行组合,形成好字符串。比如:0010,对于这个
1
1
1,它可以分别向前、向后与若干个
0
0
0进行组合(当然也可以不向前选择、或者不想后选择)。在这个例子中,若我们不考虑
k
k
k的限制,含有中间的
1
1
1的子串有
3
×
2
=
6
3\times2=6
3×2=6个。类似的,00100010中含有
3
×
4
+
4
×
2
=
20
3\times4+4\times2=20
3×4+4×2=20个好子串。在这个过程中,我们重点关注的是1左右两边
0
0
0的个数(此处在计算过程中我们进行了
+
1
+1
+1操作)。
对于一个给定的字符串 s s s,我们可以把这些 0 0 0的个数 + 1 +1 +1以后的结果记作数组 a 1 , a 2 , … , a n a_1, a_2, \dots, a_n a1,a2,…,an的内容,则 s s s中好子串的个数为 a 1 a 2 + a 2 a 3 + ⋯ + a n − 1 a n a_1a_2+a_2a_3+\dots+a_{n-1}a_n a1a2+a2a3+⋯+an−1an。此时若我们多加入一个 a n + 1 a_{n+1} an+1,就是在原来结果的基础上加上 a n a n + 1 a_na_{n+1} anan+1。换言之,假设我们用 d p [ s u m ] dp[sum] dp[sum]数组中的元素来表示好子串个数为 s u m sum sum的子串数,即 s u m = ∑ i = 1 n + 1 a n sum=\sum_{i=1}^{n+1}a_n sum=∑i=1n+1an,那么我们可以发现: d p [ s u m ] dp[sum] dp[sum]与 d p [ s u m − a n ⋅ a n − 1 ] dp[sum-a_n\cdot a_{n-1}] dp[sum−an⋅an−1]有关。
但是,并不是只有
a
n
a_n
an结尾的方案才能得到和为
s
u
m
sum
sum的结果,我们需要给
d
p
dp
dp数组加上一个维度作为限制。此处,我们以
d
p
[
s
u
m
]
[
l
a
s
t
]
dp[sum][last]
dp[sum][last]表示:和为
s
u
m
sum
sum,最后一个元素(即我们前面讨论的
a
n
+
1
a_{n+1}
an+1)为
l
a
s
t
last
last的方案数,那么我们有:
d
p
[
s
u
m
]
[
l
a
s
t
]
=
∑
i
d
p
[
s
u
m
−
i
⋅
l
a
s
t
]
[
i
]
其中, i i i的枚举范围要考虑到:
初始化 d p dp dp数组时,可以令满足 1 ≤ l a s t ≤ k 1\le last\le k 1≤last≤k的 d p [ 0 ] [ l a s t ] dp[0][last] dp[0][last]均为 1 1 1,即对应全 0 0 0字符串。
hint: 清空 d p dp dp数组需要使用循环来清空避免超时。
#include<bits/stdc++.h> using namespace std; int n, k, dp[3000][3000]; const int mod = 998244353; void solve() { cin >> n >> k; for (int i = 1; i <= n; i++) for (int j = 1; j <= k; j++) dp[i][j] = 0; int ans = 0; //对全0字符串进行初始化操作 for (int last = 1; last <= k; last++) { dp[0][last] = 1; } //根据dp方程进行计算,注意i的范围,不要忘记取模 for (int sum = 1; sum <= n; sum++) { for (int last = 1; last <= k; last++) { for (int i = 1; i * last <= sum && i + last - 1 <= k; i++) { dp[sum][last] = (dp[sum][last] + dp[sum - i * last][i]) % mod; } } } //统计满足要求的好子串个数,不要忘记取模 for (int last = 1; last <= k; last++) { ans = (ans + dp[n][last]) % mod; } cout << ans << endl; } int main() { int Case; cin >> Case; while (Case--) { solve(); } return 0; }
以下为学习交流QQ群,群号: 546235402,每周题解完成后都会转发到群中,大家可以加群一起交流做题思路,分享做题技巧,欢迎大家的加入。

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。