CF2059 Div.2

CF 2059 div.2

A Milya and Two Arrays

题目大意

给出两个长度相同的数组 $A, B$, 每个数组中的元素均重复出现 (即若 $x \in A$, 则 $x$ 在 $A$ 中出现次数大于等于 2).

现在定义 $C_i = A_i + B_i$, 问是否可以通过对 $A$ 的重新排列, 使得 $C$ 中不同元素个数不少于 3 种.

题解

手玩几个小样例不难发现, 无解当且仅当 $A, B$ 各自不同元素种类的个数和小于等于 3.

code

 1const int N = 55;
 2int n, a[N], b[N];
 3void solve()
 4{
 5    read(n);
 6    int mx = 0, mn = 1e9, cnt = 0;
 7    for (int i = 1; i <= n; i++)
 8        read(a[i]);
 9    sort(a + 1, a + n + 1);
10    cnt += unique(a + 1, a + n + 1) - a - 1;
11    for (int i = 1; i <= n; i++)
12        read(b[i]);
13    sort(b + 1, b + n + 1);
14    cnt += unique(b + 1, b + n + 1) - b - 1;
15    puts(cnt <= 3 ? "NO" : "YES");
16}
17int main()
18{
19    int T = 1;
20    cin >> T;
21    while (T--)
22        solve();
23    flushout();
24    return 0;
25}

B Cost of the Array

题目大意

给出一个长为 $n$ 的数组和一个偶数 $k$, 现在需要将该数组分成 $k$ 段子数组 (每段均连续). 接着将所有偶数段取出拼成新数组 $B$, 并在末尾加入数字 0. 定义花费为最小的 $i$ 满足 $B_i \ne i$. 求最小花费.

题解

  • 当 $n = k$ 时, 划分方式唯一, 直接模拟求解即可.
  • 当 $n \ne k$ 时, 答案不超过 2. 首先我们希望第二段的开头不是 1, 所以如果第 2 个数到第 $n - k + 2$ 个数中有不是 1 的数, 我们直接以此为第二段开头即可;否则由于 $k$ 是偶数, 第 2,3,4 位一定均是 1 ($n-k+2 \geq 4$), 所以直接取前 4 位单独成段即可 ($k \geq 4$). 而当 $k = 2$ 时, 说明该数组全是 1.

code

 1const int N=2e5+10;
 2int n,k,a[N];
 3void solve()
 4{
 5    read(n),read(k);
 6    for(int i=1;i<=n;i++)read(a[i]);
 7    if(n==k)
 8    {
 9        for(int i=2;i<=n;i+=2)
10        {
11            if(a[i]!=i/2)
12            {
13                write(i/2);
14                return;
15            }
16        }
17        write(k/2+1);
18        return;
19    }
20    for(int i=2;i<=n-k+2;i++)
21        if(a[i]!=1)
22        {
23            write(1);
24            return;
25        }
26    write(2);
27    return;
28}
29int main()
30{
31    int T;
32    cin>>T;
33    while(T--) solve();
34    flushout();
35    return 0;
36}

C Customer Service

题目大意

有 $n$ 条服务队列, 初始都为 0 个人, 接下来一共有 $n$ 个时刻, $a_{i,j} (a_{i,j} \geq 1)$ 表示第 $i$ 个队列在 $j$ 时刻来的客人.

并且在每个时刻来人后, 你必须选择恰好一条队列并将其清空.

请求出在这 $n$ 个时刻后, 这 $n$ 条队列最终人数的 MEX 最大是多少.

题解

首先考虑只有最后一个时刻被清空的队列是 0, 因为 $a_{i,j} \geq 1$. 而想要最终是 1 个人, 那么 $a_{i,n} = 1$ 且在 $n-1$ 时刻清空该队列. 如此重复可以得到最终是 $k$ 个人的队列一定满足最后 $k$ 个时刻来的人都是 1 个人并且在第 $k-1$ 时刻将其清空.

由此, 我们求出每条队列末尾连续 1 的长度, 然后从小到大贪心选取即可.

code

 1const int N = 310;
 2int T, n, a[N][N], b[N];
 3
 4int main()
 5{
 6    read(T);
 7    while (T--)
 8    {
 9        read(n);
10        for (int i = 1; i <= n; i++)
11            b[i] = 0;
12        for (int i = 1; i <= n; i++)
13            for (int j = 1; j <= n; j++)
14                read(a[i][j]);
15        for (int i = 1; i <= n; i++)
16            for (int j = n; j; j--)
17                if (a[i][j] == 1)
18                    b[i]++;
19                else
20                    break;
21        sort(b + 1,b+n+1);
22        int ans = 0;
23        for(int i=1;i<=n;i++)
24            if(b[i]>=ans)ans++;
25        write(ans);
26    }
27    flushout();
28    return 0;
29}

D Graph and Graph

题目大意

给出两个均含有 $n$ 个结点的无向图, 分别含有 $m_1, m_2$ 条边. 现在你在这两张图上各有一个初始结点 $s_1, s_2$. 每一次操作你都可以选择和你当前位置相邻的结点并移动过去 (两张图上均要移动). 设操作后你的位置是 $u_1, u_2$, 那么你这次操作的花费是 $|u_1 - u_2|$. 问在这样的操作规则下操作无限步后你的花费最小是多少, 如果是无穷请输出 -1.

题解

简单最短路题. 我们先考虑最后不是无穷的情况, 一定是有一条边在两个图中均存在, 并且可以做到在两张图上同时达到编号相同的一个端点上 (这里面其实蕴含了一个奇偶性, 但对实际做题没有影响). 考虑到一共只有 $n^2$ 个点对, 我们只要从 $(s_1, s_2)$ 从出发, 跑最短路, 当遇到上述不是无穷的情况就可以更新答案.

考虑边数一定不超过 $m_1 \times m_2$, 所以复杂度是 $O((n + m_1 \times m_2) \log(m_1 \times m_2))$. 可以通过.

code

 1const int N = 1e3 + 10;
 2int T, n, s1, s2, m1, m2, dis[N][N];
 3vector<int> e1[N], e2[N];
 4void graph_read(int &m, vector<int> *e)
 5{
 6    read(m);
 7    for (int i = 1, u, v; i <= m; i++)
 8    {
 9        read(u), read(v);
10        e[u]. push_back(v);
11        e[v]. push_back(u);
12    }
13    return;
14}
15struct nod
16{
17    int u1, u2, dis;
18    bool operator<(const nod &b) const
19    {
20        return dis > b. dis;
21    }
22};
23
24priority_queue<nod> q;
25
26int main()
27{
28    read(T);
29    while (T--)
30    {
31        read(n), read(s1), read(s2);
32        for (int i = 1; i <= n; i++)
33            e1[i]. clear(), e2[i]. clear();
34        graph_read(m1, e1);
35        graph_read(m2, e2);
36        for (int i = 1; i <= n; i++)
37            for (int j = 1; j <= n; j++)
38                dis[i][j] = 1e9 + 10;
39        dis[s1][s2] = 0;
40        q. push({s1, s2, 0});
41        int ans = 2e9;
42        while (!q. empty())
43        {
44            nod now = q. top();
45            q. pop();
46            if (now. dis != dis[now. u1][now. u2])
47                continue;
48            for (int t1 : e1[now. u1])
49                for (int t2 : e2[now. u2])
50                {
51                    if (now. u1 == now. u2 && t1 == t2)
52                        ans = min(ans, now. dis);
53                    if (dis[t1][t2] > now. dis + abs(t1 - t2))
54                    {
55                        dis[t1][t2] = now. dis + abs(t1 - t2);
56                        q. push({t1, t2, dis[t1][t2]});
57                    }
58                }
59        }
60        write(ans == 2e9 ? -1 : ans);
61    }
62    flushout();
63    return 0;
64}

E Stop Gaming

题目描述

给你 $n$ 个数组, 每个数组的长度为 $m$. 让 $i$-th 数组中的 $j$-th 元素表示为 $a_{i,j}$. 保证所有 $a_{i,j}$ 都是不同的. 在一次操作中, 您可以执行以下操作:

  • 选择某个整数 $i$ ($1 \leq i \leq n$) 和一个整数 $x$ ($1 \leq x \leq 2 \cdot n \cdot m$).
  • 对于从 $i$ 到 $n$ 依次递增的所有整数 $k$, 进行以下操作:
    1. 将元素 $x$ 添加到 $k$-th 数组的开头.
    2. 将 $k$-th 数组中最后一个元素的值赋值给 $x$.
    3. 删除 $k$-th 数组中的最后一个元素.

换句话说, 你可以在任意数组的开头插入一个元素, 之后这个数组和后面所有数组中的元素都会向右平移一个. 最后一个数组的最后一个元素会被移除.

此外, 我们还给出了目标数组 $b_{i,j}$, 保证 $b_{i,j}$ 互不相同, 求最少的操作次数将 $a_{i,j}$ 变为 $b_{i,j}$, 并输出具体方案.

题解

我们先考虑求最少的次数. 这是一个贪心的过程, 因为我们希望尽可能的利用原本就存在的数. 那么我们来考虑能被利用的数的性质, 首先被利用的数肯定是 $b_{i,j}$ 的子序列. 其次, 这些数应该是 $a_{i,j}$ 的前缀, 因为根据上述操作, 我们无法间隔的留下 $a_{i,j}$.

那么我们就希望这个前缀尽可能长, 于是考虑从左至右贪心, 找到最远的可行位置. 一个位置是可行的一方面, 其左边所有需要操作的位置都"经历过" $k$ 的倍数, 只有这样才能在该位置后面添数, 这部分就对应了代码中 tot < m - (i - 2 + m) % m - 1 && dlt 这部分. 当无法达到时直接结束. 而另一方面, 该位置后面可能也需要添加元素, 所以该位置也得"经历过" $k$ 的倍数, 对应了代码中的 tot >= m - (i - 1 + m) % m - 1. 要注意的是, 不满足该条件也需要继续往后找, 因为后面可能有位置合法.

如此往复, 我们就可以得到最长的前缀, 然后减一下就得到最少步数.

第二部分, 求具体方案. 当我们知道长度后其实就是模拟这个过程, 考虑用线段树作为辅助数据结构, 要进行区间加减, 全局查询最右边的 0 (意味着该位置需要被操作了, 并且操作该位置不会影响左边的可操作性). 当某个位置操作完时, 将其赋值为 inf 即可. 而当全局最小值不是 0 后意味着操作结束.

code

  1const int N = 3e5 + 10;
  2int T, n, m, a[N], b[N], cnt[N], ct[N], val[N], pos[N];
  3map<int, int> mp;
  4struct Tree
  5{
  6    int mn, pos, tag;
  7    void upd(int tg)
  8    {
  9        mn += tg;
 10        tag += tg;
 11        return;
 12    }
 13} tr[N << 2];
 14void pushup(int rt)
 15{
 16    if (tr[ls]. mn < tr[rs]. mn)
 17    {
 18        tr[rt]. mn = tr[ls]. mn;
 19        tr[rt]. pos = tr[ls]. pos;
 20    }
 21    else
 22    {
 23        tr[rt]. mn = tr[rs]. mn;
 24        tr[rt]. pos = tr[rs]. pos;
 25    }
 26    return;
 27}
 28void pushdown(int rt)
 29{
 30    if (tr[rt]. tag)
 31    {
 32        tr[ls]. upd(tr[rt]. tag);
 33        tr[rs]. upd(tr[rt]. tag);
 34        tr[rt]. tag = 0;
 35    }
 36}
 37void build(int rt, int l, int r)
 38{
 39    tr[rt] = {0, 0, 0};
 40    if (l == r)
 41    {
 42        tr[rt] = {val[l], l, 0};
 43        return;
 44    }
 45    int mid = (l + r) / 2;
 46    build(ls, l, mid);
 47    build(rs, mid + 1, r);
 48    pushup(rt);
 49}
 50void change(int rt, int l, int r, int L, int R, int v)
 51{
 52    if (L <= l && r <= R)
 53        return tr[rt]. upd(v);
 54    pushdown(rt);
 55    int mid = (l + r) >> 1;
 56    if (L <= mid)
 57        change(ls, l, mid, L, R, v);
 58    if (mid < R)
 59        change(rs, mid + 1, r, L, R, v);
 60    pushup(rt);
 61    return;
 62}
 63int main()
 64{
 65    read(T);
 66    while (T--)
 67    {
 68        read(n), read(m);
 69        mp. clear();
 70        // mp[0] = n * m + 1;
 71        for (int i = 1; i <= n * m; i++)
 72            read(a[i]);
 73        for (int i = 1; i <= n * m; i++)
 74            read(b[i]), mp[b[i]] = i;
 75        for (int i = 1; i <= n * m; i++)
 76            pos[i] = mp[a[i]];
 77        int len = 0;
 78        for (int i = 1, tot = 0; i <= n * m; i++)
 79        {
 80            if (pos[i - 1] >= pos[i])
 81                break;
 82            int dlt = pos[i] - pos[i - 1] - 1;
 83            cnt[i - 1] = dlt;
 84            val[i - 1] = cnt[i - 1] ? m - (i - 2 + m) % m - 1 : 1e9;
 85            if (tot < m - (i - 2 + m) % m - 1 && dlt)
 86                break;
 87            tot += dlt;
 88            if (tot >= m - (i - 1 + m) % m - 1)
 89                len = i;
 90        }
 91        cnt[len] = n * m - pos[len];
 92        val[len] = cnt[len] ? m - (len - 1 + m) % m - 1 : 1e9;
 93        for (int i = 0; i <= len; i++)
 94            ct[i] = cnt[i];
 95        write(n * m - len);
 96        build(1, 0, len);
 97        while (tr[1]. mn == 0)
 98        {
 99            int now = tr[1]. pos;
100            write((now + m - 1) / m + 1, ' '), write(b[pos[now] + ct[now]]);
101            if ((--ct[now]) == 0)
102                change(1, 0, len, now, now, 1e9);
103            if (now < len)
104                change(1, 0, len, now + 1, len, -1);
105        }
106    }
107    flushout();
108    return 0;
109}
0%