比赛题目训练系列11 (2020ICPC·小米 网络选拔赛第二场)
A. 2020
- 给定一个只含有0,1,2的字符串,现在问你可以把字符串最多分成多少个互不相交的子序列,其中每一个子序列是2020.
- 最开始很容易想到一个错误的思路,就是找到一个2,2后面紧跟一个0,0后面紧跟一个2,2后面再找紧跟一个0. 但是这样的做法不对。因为一个2020的第二个0可能给下一个2020的第一个2用,更好一些。比如
20220200
20220200
20220200 这个字符串。
#include<iostream>
using namespace std;
const int maxn = 100010;
char str[maxn];
int N, used[maxn], vis1[maxn], vis2[maxn], vis3[maxn];
bool check(int x)
{
fill(used + 1, used + N + 1, 0);
fill(vis1 + 1, vis1 + N + 1, 0);
fill(vis2 + 1, vis2 + N + 1, 0);
fill(vis3 + 1, vis3 + N + 1, 0);
int num = 0, cnt = 0;
for(int i = 1; i <= N; i++){
if(num >= x) break;
if(str[i] == '2') {
used[i] = 1;
vis1[i] = 1;
num++;
}
}
if(num < x) return false;
num = 0, cnt = 0;
for(int i = 1; i <= N; i++){
cnt += vis1[i];
if(num >= x) break;
if(!used[i] && str[i] == '0' && cnt > 0){
cnt--, num++;
used[i] = 1;
vis2[i] = 1;
}
}
if(num < x) return false;
num = 0, cnt = 0;
for(int i = 1; i <= N; i++){
cnt += vis2[i];
if(num >= x) break;
if(!used[i] && str[i] == '2' && cnt > 0){
cnt--, num++;
vis3[i] = 1;
used[i] = 1;
}
}
if(num < x) return false;
num = 0, cnt = 0;
for(int i = 1; i <= N; i++){
cnt += vis3[i];
if(num >= x) break;
if(!used[i] && str[i] == '0' && cnt > 0){
cnt--, num++;
}
}
if(num < x) return false;
return true;
}
int main()
{
while(cin >> N){
scanf("%s", str + 1);
int lb = 0, ub = N / 4;
while(ub > lb){
int mid = (lb + ub + 1) / 2;
if(check(mid)) lb = mid;
else ub = mid - 1;
}
printf("%d\n", lb);
}
return 0;
}
C. Data Structure Problem
- 题意:
- 题解:
- 线段树维护b数组的区间和, a i − s i a_i - s_i ai−si 的最大值,以及懒标记(下面会解释)。
- 如果是第一个操作,那么就单点修改 a i − s i a_i - s_i ai−si 的值。
- 如果是第二个操作,先单点修改 b 数组的值(同时维护区间和的信息),然后从 x x x 到 N N N 区间每个 a i − s i a_i - s_i ai−si 都减去 b x b_x bx 增大的值。
- 第三个操作查询即可。即 s x + m a x ( 0 , m a x { a i − s i ∣ x ≤ i ≤ n } ) s_x + max(0, max\{a_i - s_i | x \le i \le n\}) sx+max(0,max{ai−si∣x≤i≤n}).
#include<iostream>
using namespace std;
const int maxn = 200010;
typedef long long ll;
ll a[maxn], b[maxn], s[maxn];
int N, M;
struct node
{
int l, r;
ll sum, a_s, add;
}tr[maxn * 4];
void pushup(int u)
{
tr[u].a_s = max(tr[2 * u].a_s, tr[2 * u + 1].a_s);
tr[u].sum = tr[2 * u].sum + tr[2 * u + 1].sum;
}
void pushdown(int u)
{
auto & rt = tr[u], &left = tr[2 * u], & right = tr[2 * u + 1];
if(rt.add){
left.add += rt.add, left.a_s -= rt.add;
right.add += rt.add, right.a_s -= rt.add;
rt.add = 0;
}
}
void build(int u, int l, int r)
{
tr[u] = {l, r};
tr[u].add = 0;
if(l == r){
tr[u].sum = b[l], tr[u].a_s = a[l] - s[l];
}
else{
int mid = (l + r) / 2;
build(2 * u, l, mid), build(2 * u + 1, mid + 1, r);
pushup(u);
}
}
void modify1(int u, int x, int add)
{
if(tr[u].l == x && tr[u].r == x){
tr[u].a_s += add;
}
else{
pushdown(u);
int mid = (tr[u].l + tr[u].r) / 2;
if(x <= mid) modify1(2 * u, x, add);
else modify1(2 * u + 1, x, add);
pushup(u);
}
}
void modify2(int u, int x, int y)
{
if(tr[u].l == x && tr[u].r == x){
tr[u].sum = y;
}
else{
pushdown(u);
int mid = (tr[u].l + tr[u].r) / 2;
if(x <= mid) modify2(2 * u, x, y);
else modify2(2 * u + 1, x, y);
pushup(u);
}
}
void modify3(int u, int l, int r, int add)
{
if(l <= tr[u].l && tr[u].r <= r){
tr[u].a_s -= add;
tr[u].add += add;
}
else{
pushdown(u);
int mid = (tr[u].l + tr[u].r) / 2;
if(l <= mid) modify3(2 * u, l, r, add);
if(r > mid) modify3(2 * u + 1, l, r, add);
pushup(u);
}
}
ll query1(int u, int l, int r)
{
if(l <= tr[u].l && tr[u].r <= r) return tr[u].sum;
pushdown(u);
int mid = (tr[u].l + tr[u].r) / 2;
ll sum = 0;
if(l <= mid) sum += query1(2 * u, l, r);
if(r > mid) sum += query1(2 * u + 1, l, r);
return sum;
}
ll query2(int u, int l, int r)
{
if(l <= tr[u].l && tr[u].r <= r){
return tr[u].a_s;
}
pushdown(u);
int mid = (tr[u].l + tr[u].r) / 2;
ll res = -1e18;
if(l <= mid) res = max(res, query2(2 * u, l, r));
if(r > mid) res = max(res, query2(2 * u + 1, l, r));
return res;
}
int main()
{
while(cin >> N >> M){
for(int i = 1; i <= N; i++){
scanf("%lld", &a[i]);
}
for(int i = 1; i <= N; i++){
scanf("%lld", &b[i]);
s[i] = b[i] + s[i - 1];
}
build(1, 1, N);
while(M--){
int op, x;
ll y;
scanf("%d", &op);
if(op == 1){
scanf("%d%lld", &x, &y);
modify1(1, x, y - a[x]);
a[x] = y;
}
else if(op == 2){
scanf("%d%lld", &x, &y);
modify2(1, x, y);
modify3(1, x, N, y - b[x]);
b[x] = y;
}
else{
scanf("%d", &x);
ll s_x = query1(1, 1, x);
//printf("Im here01\n");
ll a_s = query2(1, 1, x);
ll res = max(s_x, s_x + a_s);
//printf("Im here02\n");
printf("%lld\n", res);
}
}
}
return 0;
}
D. Determinant
-
题意:直接看吧
-
题目范围: n ≤ 1 0 5 n \le 10^5 n≤105.
-
这个题推公式即可。这个矩阵写出来之后,把第 i 行除以 a i a_i ai,把第 j 列除以 b j b_j bj,那么对角线会变成 x / a 1 b 1 , x / a 2 b 2 , . . . , x / a n b n x / a_1b_1,x/a_2b_2,...,x/a_nb_n x/a1b1,x/a2b2,...,x/anbn,其他元素全为1.
-
这样子的话,就成了一道简单的线性代数的课后题。最后答案是 x n + x n − 1 ∑ i = 1 n a i b i x^n + x^{n-1} \sum\limits_{i=1}^{n}a_ib_i xn+xn−1i=1∑naibi.
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 100010;
typedef long long ll;
const ll mod = 1e9 + 7;
ll a[maxn], b[maxn], x;
int N;
ll mod_pow(ll x, ll n)
{
ll res = 1;
while(n){
if(n & 1) res = res * x % mod;
x = x * x % mod;
n >>= 1;
}
return res;
}
int main()
{
while(scanf("%d", &N) == 1){
scanf("%lld", &x);
for(int i = 1; i <= N; i++) scanf("%lld", &a[i]);
for(int i = 1; i <= N; i++) scanf("%lld", &b[i]);
ll ans = 0;
for(ll i = 1; i <= N; i++){
ans = (ans + mod_pow(x, N - 1) * a[i] % mod * b[i] % mod) % mod;
}
ans = (ans + mod_pow(x, N)) % mod;
printf("%lld\n", ans);
}
return 0;
}
F.
G. Shift and Reverse
- 题意:直接截图,序列长度
n
≤
1
0
5
n \le 10^5
n≤105.
- 其实你画一个 1 2 3 4 5 的序列,找一下规律就会发现,把序列绕成一个环,所有序列就是从某个元素出发,顺时针旋转得到一个字符串,逆时针旋转得到一个字符串。总共最多得到 2 n 2n 2n 个本质不同的字符串。
- 因此,一种可行的解法是,用字符串哈希将这些所得的字符串记录下来。从 i 到 i + 1 这个状态就可以推导出字符串哈希值的变化。
- 字符串哈希有一个很大的问题,就是两个不同的字符串可能哈希值相等。字符串哈希冲突的概率和生日悖论是一样的。有这么一个结论,当取模的值为 P P P,字符串长度为 P \sqrt P P 时,冲突的概率时 50%.
- 因此,这道题的模数可以选择 4179340454199820289LL。
- 用自然溢出的话,可以用 abba baab baab abba 这样的字符串卡掉。因此切记:自然溢出是不好的,不用自然溢出用大质数。
- 注意那个乘法的方法,挺牛逼的。
- 以后用扩展欧几里得方法求逆元,那个 O ( log n ) O(\log n) O(logn) 的复杂度是伪的,这个比费马小定理求逆元更快一些。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<unordered_set>
using namespace std;
typedef long long ll;
const int maxn = 100010;
const ll P = 131, mod = 4179340454199820289LL;
typedef long double llf;
ll p[maxn] = {1}, w[maxn];
int N;
ll add(ll x, ll y)
{
return (x + y) % mod;
}
ll mul(ll x, ll y)
{
ll tmp = x * y - ll(llf(x) * llf(y) / llf(mod)) * mod;
return tmp < 0 ? tmp + mod : (tmp < mod ? tmp : tmp - mod);
}
ll del(ll x, ll y)
{
return ((x - y) % mod + mod) % mod;
}
ll exgcd(ll a, ll b, ll& x, ll& y)
{
if(!b){
x = 1, y = 0;
return a;
}
ll d = exgcd(b, a % b, y, x);
y = del(y, mul(a / b, x));
return d;
}
ll inv_P;
int main()
{
ll _;
exgcd(P, mod, inv_P, _);
inv_P = (inv_P % mod + mod) % mod;
for(int i = 1; i < maxn; i++){
p[i] = mul(p[i - 1], P);
}
while(cin >> N){
unordered_set<ll> S;
for(int i = 1; i <= N; i++) scanf("%lld", &w[i]);
ll res = 0;
//逆时针的字符串环
res = mul(w[1], P);
for(int i = 2; i <= N; i++){
res = add(res, mul(w[N - i + 2], p[i]));
}
S.insert(res);
for(int i = 2; i <= N; i++){
res = del(res, mul(w[i], p[N]));
res = mul(res, P);
res = add(res, mul(w[i], P));
S.insert(res);
}
//顺时针的字符串环
res = 0;
for(int i = 1; i <= N; i++){
res = add(res, mul(w[i], p[i]));
}
S.insert(res);
for(int i = 2; i <= N; i++){
res = del(res, mul(w[i - 1], P));
res = mul(res, inv_P);
res = add(res, mul(w[i - 1], p[N]));
S.insert(res);
}
cout << S.size() << endl;
}
return 0;
}
- 还有一种方法是:最开始答案是2n,如果顺时针走的话,相当于看看最小循环节是多少,即用 kmp,最小循环节是 i − n e [ i ] i - ne[i] i−ne[i]。 逆时针走的话,就是看看能否分成两个回文串。分成一个回文串就把答案 -1.
H. Knapsack
-
01背包,数据范围是物品个数 n ≤ 2 e 5 n \le 2e5 n≤2e5,背包容量 M ≤ 2 e 5 M \le 2e5 M≤2e5,单个物品的重量 w ≤ 100 w \le 100 w≤100。
-
有一个结论是,当背包的剩余容量 > 100 时,那么我们可以贪心地选择 v w \frac{v}{w} wv 更大的数,此时这个物品一定会包含在最终的答案里面。
不过为什么呀? -
当然上面这么思考是不对的。只是因为数据比较水。比如题目给了一大堆 v = 2 , w = 2 v = 2, w = 2 v=2,w=2 的物品,而给了一个 v = 3 , w = 3 v = 3, w = 3 v=3,w=3 的物品,背包容量是 M 是一个偶数,那么如果先选了 3 后必然凑不出来最优答案了。
-
这道题的正解是一个看不懂的东西
-
另外比较 v 1 w 1 > v 2 w 2 \frac{v_1}{w_1} > \frac{v_2}{w_2} w1v1>w2v2,可以转化为 v 1 ∗ w 2 > v 2 ∗ w 1 v_1*w_2 > v_2*w_1 v1∗w2>v2∗w1 来比较。
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 200010;
typedef long long ll;
struct P
{
ll w, v;
}item[maxn];
bool cmp(const P& p1, const P& p2)
{
//if (p1.v * p2.w == p2.v * p1.w) return p1.w < p2.w;
return p1.v * p2.w > p2.v * p1.w;
}
int N, M;
//bool selected[maxn];
ll ans, f[110];
int main()
{
while(cin >> N >> M){
memset(f, 0, sizeof f);
ans = 0;
for(int i = 1; i <= N; i++){
scanf("%lld%lld", &item[i].w, &item[i].v);
}
sort(item + 1, item + N + 1, cmp);
int id;
for(id = 1; id <= N; id++){
if(M <= 100) break;
ans += item[id].v, M -= item[id].w;
}
for(int i = id; i <= N; i++){
for(int j = M; j >= item[i].w; j--){
f[j] = max(f[j], f[j - item[i].w] + item[i].v);
}
}
ans += f[M];
printf("%lld\n", ans);
}
return 0;
}
I. Subsequence Pair
- 给两个字符串,分别找到一个子序列,让第一个字符串的子序列的字典序小于第二个字符串的子序列;然后问两个子序列长度之和最大是多少。
- 我们这样考虑:
- 如果子序列 s [ i ] < t [ i ] s[i] < t[i] s[i]<t[i],那么 s s s 和 t t t 从 i + 1 i+1 i+1 开始就不重要了。因为 s 的字典序一定大于 t
- 如果当前的 s [ i ] = t [ i ] s[i] = t[i] s[i]=t[i],那么一种可行的答案就是 t 再加上后面剩余的所有字符。
- 因此,我们可以这样子,令 f ( i , j ) f(i,j) f(i,j) 就是最长公共子序列的长度,这样子可以把前两种类型的答案都计算到。
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 2010;
int ans;
char s1[maxn], s2[maxn];
int f[maxn][maxn];
int main()
{
while(scanf("%s", s1 + 1) == 1){
scanf("%s", s2 + 1);
int N = strlen(s1 + 1), M = strlen(s2 + 1);
for(int i = 1; i <= N; i++){
fill(f[i], f[i] + M + 1, 0);
}
ans = M;
for(int i = 1; i <= N; i++){
for(int j = 1; j <= M; j++){
//如果当前是 s1[i] < s2[j] 的话,那么后面可以出现 s1[i + 1] > s2[j + 1]
if(s1[i] < s2[j]) {
ans = max(2 * (f[i - 1][j - 1] + 1) + N - i + M - j, ans);
}
//如果当前是 s1[i] >= s2[j] 的话,那么此时就和最长公共子序列的做法是一样的。
else if(s1[i] == s2[j]){
f[i][j] = f[i - 1][j - 1] + 1;
ans = max(2 * f[i][j] + M - j, ans);
}
else f[i][j] = max(f[i - 1][j], f[i][j - 1]);
}
}
printf("%d\n", ans);
}
return 0;
}
/*
azz
bcd
*/