CodeForces1602

Two Subsequences

解析:

题目大意

给你一个字符串 \(s\)。你需要两个非空字符串 \(a\) 和 \(b\) 并且满足下面的条件:

  1. 字符串 \(a\) 和 \(b\) 都是 \(s\) 的子序列。
  2. 对于原字符串的每一个字符,必须属于 \(a\) 和 \(b\) 之一。
  3. \(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:


Codeforces Round #751 (Div. 2)/CodeForces1602的更多相关文章

  1. Codeforces Round #366 (Div. 2) ABC

    Codeforces Round #366 (Div. 2) A I hate that I love that I hate it水题 #I hate that I love that I hate ...

  2. Codeforces Round #354 (Div. 2) ABCD

    Codeforces Round #354 (Div. 2) Problems     # Name     A Nicholas and Permutation standard input/out ...

  3. Codeforces Round #368 (Div. 2)

    直达–>Codeforces Round #368 (Div. 2) A Brain’s Photos 给你一个NxM的矩阵,一个字母代表一种颜色,如果有”C”,”M”,”Y”三种中任意一种就输 ...

  4. cf之路,1,Codeforces Round #345 (Div. 2)

     cf之路,1,Codeforces Round #345 (Div. 2) ps:昨天第一次参加cf比赛,比赛之前为了熟悉下cf比赛题目的难度.所以做了round#345连试试水的深浅.....   ...

  5. Codeforces Round #279 (Div. 2) ABCDE

    Codeforces Round #279 (Div. 2) 做得我都变绿了! Problems     # Name     A Team Olympiad standard input/outpu ...

  6. Codeforces Round #262 (Div. 2) 1003

    Codeforces Round #262 (Div. 2) 1003 C. Present time limit per test 2 seconds memory limit per test 2 ...

  7. Codeforces Round #262 (Div. 2) 1004

    Codeforces Round #262 (Div. 2) 1004 D. Little Victor and Set time limit per test 1 second memory lim ...

  8. Codeforces Round #371 (Div. 1)

    A: 题目大意: 在一个multiset中要求支持3种操作: 1.增加一个数 2.删去一个数 3.给出一个01序列,问multiset中有多少这样的数,把它的十进制表示中的奇数改成1,偶数改成0后和给 ...

  9. Codeforces Round #268 (Div. 2) ABCD

    CF469 Codeforces Round #268 (Div. 2) http://codeforces.com/contest/469 开学了,时间少,水题就不写题解了,不水的题也不写这么详细了 ...

随机推荐

  1. BS架构与CS架构

    BS与CS的区别:1.BS是标准规范的,CS的协议自定义:2.BS核心运算都在服务器端,CS客户端和服务器端都可以运算:3.BS只需要部署服务器端,CS需要同时升级客户端和服务器端. CS(Clien ...

  2. Linux 03 用户管理

    参考源 https://www.bilibili.com/video/BV187411y7hF?spm_id_from=333.999.0.0 版本 本文章基于 CentOS 7.6 概述 Linux ...

  3. bash脚本里的-h是什么意思?

    问题描述 我在看脚本的时候,看到了下面代码 其中的-h "$PRG"我一时没明白是在判断什么东西.然后翻阅了一下菜鸟教程和其他教程,都没有说. 问题解决 -h其实是在判断这个文件是 ...

  4. 【NOI P模拟赛】华莱士CNHLS(容斥,数论分块)

    题意 出题人吃华 莱 士拉肚子了,心情不好,于是出了一道题面简单的难题. 共 T T T 组数据,对正整数 n n n 求 F ( n ) = ∑ i = 1 n μ 2 ( i ) i F(n)=\ ...

  5. 青源Talk第8期|苗旺:因果推断,观察性研究和2021年诺贝尔经济学奖

    biobank 英国的基金数据因果推断和不同的研究互相论证,而非一个研究得到的接了就行.数据融合,data fusion,同一个因果问题不同数据不同结论,以及历史上的数据,来共同得到更稳健.更高效的推 ...

  6. 第四十七篇:webpack的基本使用(一) --安装和配置webpack

    好家伙, 1.webpack的基本使用 写个例子:实现一个奇偶行变色列表 步骤如下: ① 新建项目空白目录,并运行 npm init-y命令,初始化包管理配置文件 package.json② 新建sr ...

  7. Elasticsearch7.6.2 RestHighLevelClient查询用法 must should(and or 关系)

    1. 引入jar <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId&g ...

  8. 从代码到发包,一个程序全搞定!Gitea 推出软件包自托管功能 Package Registry

    2022 年 7 月的最后一天,随着 Gitea 1.17.0 版本的正式发布,Gitea 开源社区推出了一项名为 Package Registry 的包管理功能,与 Gitea 代码仓库无缝集成,类 ...

  9. 天天写SQL,这些神奇的特性你知道吗?

    摘要:不要歪了,我这里说特性它不是 bug,而是故意设计的机制或语法,你有可能天天写语句或许还没发现原来还能这样用,没关系我们一起学下涨姿势. 本文分享自华为云社区<[云驻共创]天天写 SQL, ...

  10. 2021年3月-第01阶段-Linux基础-Linux系统的启动流程

    Linux系统的启动流程 理解Linux操作系统启动流程,能有助于后期在企业中更好的维护Linux服务器,能快速定位系统问题,进而解决问题. 上图为Linux操作系统启动流程 1.加载BIOS 计算机 ...