Codeforces Round #751 (Div. 2)/CodeForces1602

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:


posted @ 2022-08-04 11:54  TheDarkEmperor  阅读(23)  评论(0编辑  收藏  举报