Codeforces Round #751 (Div. 2)/CodeForces1602
CodeForces1602
Two Subsequences
解析:
题目大意
给你一个字符串 \(s\)。你需要两个非空字符串 \(a\) 和 \(b\) 并且满足下面的条件:
- 字符串 \(a\) 和 \(b\) 都是 \(s\) 的子序列。
- 对于原字符串的每一个字符,必须属于 \(a\) 和 \(b\) 之一。
- \(a\) 是所有满足条件的字符串中字典序最小的。
给你 \(s\),输出 \(a\) 和 \(b\)。
思路:
字典序最小,首先需要长度最小,所以 \(a\) 字符串最多只要一个字符就行,那么只在字符串中选择字典序最小的字符当做 \(a\) 即可,剩余作为 \(b\) 即可。
code
#include <bits/stdc++.h>
using namespace std;
const int N = 100 + 10;
const int INF = 0x3f3f3f3f;
inline int read ()
{
int x = 0, f = 1;
char ch = getchar ();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar (); }
while (ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar (); }
return x * f;
}
int n;
char s[N];
void solve ()
{
scanf ("%s", s + 1);
n = strlen (s + 1);
int mn = INF;
for (int i = 1; i <= n; i++) mn = min (mn, int(s[i] - 'a'));
printf ("%c ", char (mn + 'a'));
bool flag = false;
for (int i = 1; i <= n; i++) { if (!flag && int(s[i] - 'a') == mn) { flag = true; continue; } printf ("%c", s[i]); }
printf ("\n");
}
signed main()
{
int t = read ();
while (t--) solve ();
return 0;
}
Divine Array
解析:
题目大意:
给定一个序列,一次转换是将一个数变成这个数在这个序列中出现的次数。
序列 \(\{2,1,1,4,3,1,2\}\) 中,\(2\) 出现 \(2\) 次,\(1\) 出现 \(3\) 次,\(3\) 和 \(4\) 出现 \(1\) 次,那么这个序列进行一次转换之后就变成了 \(\{2,3,3,1,1,3,2\}\),同理,进行两次转换后是 \(\{2,3,3,2,2,3,2\}\),进行三次转换后是 \(\{4,3,3,4,4,3,4\}\)。
有 \(q\) 次询问,每次询问第 \(x\) 个位置的元素经过 \(k\) 次转换之后是什么。
思路:
先给出个显然的结论:这个序列在变化 \(\log n\) 次后就会稳定。
考虑每次变化,实际上是两个出现次数相同的数合并的过程,那么每次合并完成新数的出现次数就会变成原数的两倍,即最多 \(\log\) 次就会令整个序列变成一个数。
证毕。
考虑暴力 \(\log\) 次变化后的序列,离线处理答案即可。
时间复杂度 \(\mathcal O(n\log n+q)\)。
code:
#include <bits/stdc++.h>
using namespace std;
const int N = 2000 + 10;
const int M = 1e5 + 10;
const int INF = 0x3f3f3f3f;
const int mods = 998244353;
typedef pair <int, int> pii;
inline int read ()
{
int x = 0, f = 1;
char ch = getchar ();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar (); }
while (ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar (); }
return x * f;
}
int n, m;
int a[N];
struct Query {
int x, k, id;
bool operator < (const Query &A) const {
return k < A.k;
}
}q[M];
int buttle[N], ans[M];
void solve ()
{
n = read ();
for (int i = 1; i <= n; i++) a[i] = read ();
m = read ();
for (int i = 1; i <= m; i++) q[i].x = read (), q[i].k = read (), q[i].id = i;
sort (q + 1, q + m + 1);
int now = 1;
for (int cnt = 0; ; cnt++)
{
while (now <= m && q[now].k <= cnt) ans[q[now].id] = a[q[now].x], now++;
for (int i = 1; i <= n; i++) buttle[i] = 0;
for (int i = 1; i <= n; i++) buttle[a[i]]++;
bool flag = true;
for (int i = 1; i <= n; i++) if (buttle[i] != i && buttle[i]) flag = false;
if (flag) break;
for (int i = 1; i <= n; i++) a[i] = buttle[a[i]];
}
while (now <= m) ans[q[now].id] = a[q[now].x], now++;
for (int i = 1; i <= m; i++) printf ("%lld\n", ans[i]);
}
signed main()
{
int t = read ();
while (t--) solve ();
return 0;
}
Array Elimination
解析:
题目大意
给你一个由非负整数组成的长度为 \(n\) 的序列 \(a_1,a_2,\cdots,a_n\),你可以先选择一个整数 \(k\),并在原序列中选择 \(k\) 个位置 \(i_1,i_2,\cdots i_k\),设 \(x=a_{i_1}\) & \(a_{i_2}\) & \(\cdots\) & \(a_{i_k}\),令它们全部减去 \(x\),求可以通过若干次操作使得序列中每个元素都等于 \(0\) 的所有可行的 \(k\)。
思路:
考虑拆位,对于每一个二进制位分开考虑,很明显,如果当前二进制位有 \(cnt\) 个 \(1\),那么选择的 \(k\) 必定需要是 \(cnt\) 的因子,因为如果剩余 \(1\) 的个数小于 \(k\) 那么这 \(cnt\) 个 \(1\) 就不可能被消掉了。
原问题转化为 \(\log\) 位的 \(cnt\) 的公约数,可以先求出来所有 \(cnt\) 的 \(\gcd\),答案为 \(\gcd\) 的所有因数。
code
#include <bits/stdc++.h>
#define eb emplace_back
using namespace std;
const int N = 2e5 + 10;
const int INF = 0x3f3f3f3f;
typedef pair <int, int> pii;
inline int read ()
{
int x = 0, f = 1;
char ch = getchar ();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar (); }
while (ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar (); }
return x * f;
}
int n;
int a[N];
vector <int> vec, ans;
int gcd (int a, int b) { return !b ? a : gcd (b, a % b); }
void solve ()
{
vec.clear (), ans.clear ();
n = read ();
int Or = 0;
for (int i = 1; i <= n; i++) a[i] = read (), Or |= a[i];
if (!Or)
{
for (int i = 1; i <= n; i++) printf ("%lld ", i);
puts ("");
return ;
}
int k;
for (int i = 30; i >= 0; i--) if (Or & (1 << i)) { k = i; break; }
for (; k >= 0; k--)
{
int cnt = 0;
for (int i = 1; i <= n; i++) cnt += ((a[i] >> k) & 1);
if (cnt) vec.eb (cnt);
}
int g = vec[0];
for (int i = 1; i < vec.size (); i++) g = gcd (g, vec[i]);
for (int i = 1; i * i <= g; i++)
if (g % i == 0)
{
ans.eb (i);
if (i * i != g) ans.eb (g / i);
}
sort (ans.begin (), ans.end ());
for (int i = 0; i < ans.size (); i++) printf ("%d ", ans[i]);
puts ("");
}
signed main()
{
int t = read ();
while (t--) solve ();
return 0;
}
Frog Traveler
解析:
题目大意:
你是一直青蛙,最一开始在井底深 \(d\) 处,当你处于深度 \(i\) 时,你每次可以跳 \(h\in[0,a_i]\) 米,但当你跳到深度 \(j\) 米后,你将会下滑 \(b_j\) 米,请问最少要跳多少次才能到达井外(0 处)。
思路:
平衡树优化bfs,我们考虑暴力的过程,每次从当前点往前枚举 \([i-a_i,i-1]\),更新跳到这些位置后下滑到的深度所需要的跳跃次数,并把这些的新的位置加入队列。
我们发现,这样如果枚举的复杂度上限是 \(\mathcal O(n^2)\)(虽然达不到这标准并且这题可以卡常过去,但我们应追求正解),无法通过,考虑每次枚举跳到的位置实际上是一个区间,这就启发了可以用平衡树维护当前还没有被跳到的过的深度,那么在每次枚举完成后将这段区间直接在平衡树上删除,这样每个点最多会被删除一次。
优化完的代码时间复杂度 \(\mathcal O(n\log n)\)(细节较多,非常阴间的题)。
code:
#include <bits/stdc++.h>
#define eb emplace_back
using namespace std;
const int N = 3e5 + 10;
const int INF = 0x3f3f3f3f3f3f3f3f;
typedef pair <int, int> pii;
inline int read ()
{
int x = 0, f = 1;
char ch = getchar ();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar (); }
while (ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar (); }
return x * f;
}
int n, root;
int a[N], b[N];
int dp[N];
queue <int> que;
int lst[N], lst2[N];
vector <int> ans;
struct fhq_treap {
int node;
int x, y, z;
struct Tree {
int ls, rs;
int key, val, siz, pos;
#define ls(x) tree[x].ls
#define rs(x) tree[x].rs
#define key(x) tree[x].key
#define val(x) tree[x].val
#define siz(x) tree[x].siz
#define pos(x) tree[x].pos
} tree[N];
inline void pushup (int x) { siz(x) = siz(ls(x)) + siz(rs(x)) + 1; }
int new_node (int v, int x)
{
siz(++node) = 1;
key(node) = rand ();
val(node) = v;
pos(node) = x;
return node;
}
int merge (int x, int y)
{
if (!x || !y) return x + y;
if (key(x) < key(y)) { rs(x) = merge (rs(x), y); pushup (x); return x; }
else { ls(y) = merge (x, ls(y)); pushup (y); return y; }
}
void split (int now, int k, int &x, int &y)
{
if (!now) {x = y = 0; return ; }
if (val(now) <= k) x = now, split (rs(now), k, rs(x), y);
else y = now, split (ls(now), k, x, ls(y));
pushup (now);
}
void insert (int v, int p)
{
x = y = 0;
split (root, v, x, y);
root = merge (merge (x, new_node (v, p)), y);
}
void dfs (int now, int x)
{
if (!now) return ;
if (dp[pos(now)] > dp[x] + 1)
que.push (pos(now)), lst[pos(now)] = x, lst2[pos(now)] = val(now);
dp[pos(now)] = min (dp[pos(now)], dp[x] + 1);
if (!val(now)) { lst[pos(now)] = x; return ; }
dfs (ls(now), x);
dfs (rs(now), x);
}
void solve (int p)
{
int l = p - a[p] - 1, r = p - bool(a[p]);
split (root, l, x, y);
split (y, r, y, z);
dfs (y, p);
root = merge (x, z);
}
} T;
signed main()
{
n = read ();
for (int i = 1; i <= n; i++) a[i] = read ();
for (int i = 1; i <= n; i++) b[i] = read (), T.insert (i, i + b[i]);
memset (dp, 0x3f, sizeof (dp));
dp[n] = 0;
que.push (n); T.insert (0, 0);
while (!que.empty ())
{
int x = que.front (); que.pop ();
T.solve (x);
if (dp[0] != INF) break;
}
if (dp[0] == INF) printf ("-1\n");
else
{
printf ("%d\n", dp[0]);
int now = 0;
while (now != n)
{
ans.eb (lst2[now]);
now = lst[now];
}
for (int i = ans.size () - 1; i >= 0; i--) printf ("%d ", ans[i]);
}
return 0;
}
Optimal Insertion
解析:
题目大意:
给你两个序列 \(a,b\),长度分别为 \(n,m\)。现在要把 \(b\) 序列中的每个数插入 \(a\),得到一个长度为 \(n+m\) 的序列 \(c\),求逆序对个数最少的插入方法。逆序对的定义为点对 \((i,j)\) 满足 \(i<j\),\(a_i>a_j\)。
注意,\(b\) 序列可以随意插入,但 \(a\) 序列对应顺序不应发生改变。
思路:
我们定义 \(b_i\) 的最优决策位置的意义是满足在只把 \(b_i\) 插入原序列 \(a\) 中后,形成的逆序对个数最少的位置。
那么有一个结论:大数的最优决策点一定在较小数的最优决策点后面,那么最优解所对应的 \(b\) 在 \(a\) 中的顺序也应为升序排列,这样就可以对 \(b\) 按升序排序后依次插入 \(a\)。
考虑证明一下这个结论:设 \(b_i\) 的最优决策点为 \(p_i\),\(b_j\) 的最优决策点为 \(p_j\),满足 \(b_i>b_j\) 且 \(p_i<p_j\),那么考虑 \(p_i\) 之前以及 \(p_j\) 之后的数都不会对逆序对产生贡献,所以考虑 \([p_i,p_j]\) 之间的数,我们考虑分为三类:\(x> b_i\) 的个数记为 \(cnt_1\),\(b_j\leq x\leq b_i\) 的个数记为 \(cnt_2\),以及 \(x<p_j\) 的记为 \(cnt_3\),那么根据最优决策点的定义,有:\(cnt_1+cnt_2<cnt_3\),即 \(b_j\) 放在 \(p_j\) 的位置产生的逆序对小于放在 \(p_i\) 位置逆序对的个数。那么同样有 \(cnt_2+cnt_3<cnt_1\)(考虑 \(b_i\) 放在 \(p_i,p_j\)),那么与 \(cnt_1+cnt_2\) 矛盾,\(b_i>b_j\) 且 \(p_i<p_j\) 的假设不成立,则有 \(p_i>p_j\)。
这个结论并不能提供具体算法,但可以告诉我们插入的时候的顺序,并且 \(b\) 序列之间不会产生逆序对,也就是说我们只需要计算 \(a\) 内部的逆序对以及 \(a\) 与 \(b\) 产生的逆序对。
前者的计算是平凡的,可以直接 \(\mathcal O(n\log n)\) 树状数组计算,我们考虑如何计算后者。
我们维护一个长度为 \(n+1\) 的线段树,维护放当前 \(b_i\) 时在序列 \(a\) 对应位置产生的贡献,那么对于 \(a_j>b_i\) 的贡献将贡献在 \([j+1,n+1]\),对于 \(a_j<b_i\) 的贡献区间应为 \([1,j]\)。
由于 \(b\) 是单调不降的,所以我们可以动态维护 \(a_j\) 产生贡献的方向,具体看代码。
原问题变为区间 \(\pm 1\),全局最小值。
总时间复杂度 \(\mathcal O((n+m)\log n+n\log n)\)。
code:
#include <bits/stdc++.h>
#define eb emplace_back
#define lob lower_bound
using namespace std;
const int N = 1e6 + 10;
const int INF = 0x3f3f3f3f;
const int mods = 1e9 + 7;
typedef pair <int, int> pii;
inline int read ()
{
int x = 0, f = 1;
char ch = getchar ();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar (); }
while (ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar (); }
return x * f;
}
int n, m;
int a[N], b[N];
int ls[N << 1], len;
vector <int> vec[N << 1];
int buttle[N << 1];
struct Seg_tree {
#define lson rt << 1
#define rson rt << 1 | 1
struct seg_tree {
int mn, tag;
#define mn(rt) tree[rt].mn
#define tag(rt) tree[rt].tag
} tree[N << 2];
inline void pushup (int rt) { mn(rt) = min(mn(lson), mn(rson)); }
inline void spread (int rt)
{
if (tag(rt) != 0)
{
mn(lson) += tag(rt); mn(rson) += tag(rt);
tag(lson) += tag(rt); tag(rson) += tag(rt);
tag(rt) = 0;
}
}
void modify (int rt, int l, int r, int L, int R, int k)
{
if (L <= l && r <= R) { mn(rt) += k; tag(rt) += k; return ; }
int mid = (l + r) >> 1;
spread (rt);
if (L <= mid) modify (lson, l, mid, L, R, k);
if (R > mid) modify (rson, mid + 1, r, L, R, k);
pushup (rt);
}
} T;
struct BIT {
#define lowbit(x) x & (-x)
int tree[N << 1];
void add (int x, int val) { for (; x <= len; x += lowbit(x)) tree[x] += val; }
int query (int x) { int res = 0; for (; x > 0; x -= lowbit(x)) res += tree[x]; return res; }
} T2;
void solve ()
{
len = 0;
n = read (), m = read ();
for (int i = 1; i <= n; i++) a[i] = read (), ls[++len] = a[i];
for (int i = 1; i <= m; i++) b[i] = read (), ls[++len] = b[i];
sort (ls + 1, ls + len + 1);
sort (b + 1, b + m + 1);
len = unique (ls + 1, ls + len + 1) - ls - 1;
long long ans = 0;
for (int i = n; i >= 1; i--)
{
a[i] = lob (ls + 1, ls + len + 1, a[i]) - ls;
vec[a[i]].eb (i);
T.modify (1, 1, n + 1, i + 1, n + 1, 1);
ans += (long long)T2.query(a[i] - 1); T2.add (a[i], 1);
}
for (int i = 1; i <= m; i++) b[i] = lob (ls + 1, ls + len + 1, b[i]) - ls, buttle[b[i]]++;
int now = 1;
for (int i = 1; i <= m; i += buttle[b[i]])
{
while (now < b[i] && !vec[now].empty ())
{
for (int j = 0; j < vec[now].size (); j++)
T.modify (1, 1, n + 1, vec[now][j] + 1, n + 1, -1),
T.modify (1, 1, n + 1, 1, vec[now][j], 1);
now++;
}
if (now == b[i])
for (int j = 0; j < vec[now].size (); j++)
T.modify (1, 1, n + 1, vec[now][j] + 1, n + 1, -1);
ans += 1ll * T.tree[1].mn * buttle[b[i]];
if (now == b[i])
for (int j = 0; j < vec[now].size (); j++)
T.modify (1, 1, n + 1, 1, vec[now][j], 1);
now++;
}
for (int i = 1; i <= n + m; i++) T2.tree[i] = 0, buttle[i] = 0, vec[i].clear ();
for (int i = 1; i <= (n << 2); i++) T.tree[i].mn = T.tree[i].tag = 0;
printf ("%lld\n", ans);
}
signed main()
{
int t = read ();
while (t--) solve ();
return 0;
}
6
解析:
题目大意:
咕咕咕
思路:
code: